mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
commit
0f998615ad
44 changed files with 6857 additions and 2672 deletions
|
@ -40,6 +40,36 @@ class Thumbnail1(Scene):
|
|||
self.add(text)
|
||||
|
||||
|
||||
class AltThumbnail1(Scene):
|
||||
def construct(self):
|
||||
N = 20
|
||||
n_trials = 10000
|
||||
p = 0.7
|
||||
outcomes = (np.random.random((N, n_trials)) < p).sum(0)
|
||||
counts = []
|
||||
for k in range(N + 1):
|
||||
counts.append((outcomes == k).sum())
|
||||
|
||||
hist = Histogram(
|
||||
counts,
|
||||
y_max=0.3,
|
||||
y_tick_freq=0.05,
|
||||
y_axis_numbers_to_show=[10, 20, 30],
|
||||
x_label_freq=10,
|
||||
)
|
||||
hist.set_width(FRAME_WIDTH - 1)
|
||||
hist.bars.set_submobject_colors_by_gradient(YELLOW, YELLOW, GREEN, BLUE)
|
||||
hist.bars.set_stroke(WHITE, 2)
|
||||
|
||||
title = TextMobject("Binomial distribution")
|
||||
title.set_width(12)
|
||||
title.to_corner(UR, buff=0.8)
|
||||
title.add_background_rectangle()
|
||||
|
||||
self.add(hist)
|
||||
self.add(title)
|
||||
|
||||
|
||||
class Thumbnail2(Scene):
|
||||
def construct(self):
|
||||
axes = self.get_axes()
|
||||
|
@ -1827,8 +1857,8 @@ class AskAboutUnknownProbabilities(Scene):
|
|||
|
||||
def show_many_coins(self, n_rows, n_cols):
|
||||
coin_choices = VGroup(
|
||||
get_coin(BLUE_E, "H"),
|
||||
get_coin(RED_E, "T"),
|
||||
get_coin("H"),
|
||||
get_coin("T"),
|
||||
)
|
||||
coin_choices.set_stroke(width=0)
|
||||
coins = VGroup(*[
|
||||
|
@ -1873,10 +1903,10 @@ class AskProbabilityOfCoins(Scene):
|
|||
condition = VGroup(
|
||||
TextMobject("If you've seen"),
|
||||
Integer(80, color=BLUE_C),
|
||||
get_coin(BLUE_E, "H").set_height(0.5),
|
||||
get_coin("H").set_height(0.5),
|
||||
TextMobject("and"),
|
||||
Integer(20, color=RED_C),
|
||||
get_coin(RED_E, "T").set_height(0.5),
|
||||
get_coin("T").set_height(0.5),
|
||||
)
|
||||
condition.arrange(RIGHT)
|
||||
condition.to_edge(UP)
|
||||
|
@ -1886,7 +1916,7 @@ class AskProbabilityOfCoins(Scene):
|
|||
"\\text{What is }",
|
||||
"P(", "00", ")", "?"
|
||||
)
|
||||
coin = get_coin(BLUE_E, "H")
|
||||
coin = get_coin("H")
|
||||
coin.replace(question.get_part_by_tex("00"))
|
||||
question.replace_submobject(
|
||||
question.index_of_part_by_tex("00"),
|
||||
|
@ -1899,10 +1929,7 @@ class AskProbabilityOfCoins(Scene):
|
|||
random.shuffle(values)
|
||||
|
||||
coins = VGroup(*[
|
||||
get_coin(
|
||||
BLUE_E if symbol == "H" else RED_E,
|
||||
symbol
|
||||
)
|
||||
get_coin(symbol)
|
||||
for symbol in values
|
||||
])
|
||||
coins.arrange_in_grid(10, 10, buff=MED_SMALL_BUFF)
|
||||
|
@ -3265,8 +3292,8 @@ class StateIndependence(Scene):
|
|||
class IllustrateBinomialSetupWithCoins(Scene):
|
||||
def construct(self):
|
||||
coins = [
|
||||
get_coin(BLUE_E, "H"),
|
||||
get_coin(RED_E, "T"),
|
||||
get_coin("H"),
|
||||
get_coin("T"),
|
||||
]
|
||||
|
||||
coin_row = VGroup()
|
||||
|
@ -3289,7 +3316,7 @@ class IllustrateBinomialSetupWithCoins(Scene):
|
|||
"k": GREEN,
|
||||
}
|
||||
)
|
||||
heads = get_coin(BLUE_E, "H")
|
||||
heads = get_coin("H")
|
||||
template = prob_label.get_part_by_tex("00")
|
||||
heads.replace(template)
|
||||
prob_label.replace_submobject(
|
||||
|
|
File diff suppressed because it is too large
Load diff
2150
from_3b1b/active/bayes/beta3.py
Normal file
2150
from_3b1b/active/bayes/beta3.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,11 @@ import scipy.stats
|
|||
CMARK_TEX = "\\text{\\ding{51}}"
|
||||
XMARK_TEX = "\\text{\\ding{55}}"
|
||||
|
||||
COIN_COLOR_MAP = {
|
||||
"H": BLUE_E,
|
||||
"T": RED_E,
|
||||
}
|
||||
|
||||
|
||||
class Histogram(Group):
|
||||
CONFIG = {
|
||||
|
@ -171,29 +176,6 @@ def get_random_process(choices, shuffle_time=2, total_time=3, change_rate=0.05,
|
|||
return container
|
||||
|
||||
|
||||
def get_coin(color, symbol):
|
||||
coin = VGroup()
|
||||
circ = Circle()
|
||||
circ.set_fill(color, 1)
|
||||
circ.set_stroke(WHITE, 1)
|
||||
circ.set_height(1)
|
||||
label = TextMobject(symbol)
|
||||
label.set_height(0.5 * circ.get_height())
|
||||
label.move_to(circ)
|
||||
coin.add(circ, label)
|
||||
coin.symbol = symbol
|
||||
coin.lock_triangulation()
|
||||
return coin
|
||||
|
||||
|
||||
def get_random_coin(**kwargs):
|
||||
coins = VGroup(
|
||||
get_coin(BLUE_E, "H"),
|
||||
get_coin(RED_E, "T"),
|
||||
)
|
||||
return get_random_process(coins, **kwargs)
|
||||
|
||||
|
||||
def get_die_faces():
|
||||
dot = Dot()
|
||||
dot.set_width(0.15)
|
||||
|
@ -242,6 +224,69 @@ def get_random_card(height=1, **kwargs):
|
|||
return get_random_process(cards, **kwargs)
|
||||
|
||||
|
||||
# Coins
|
||||
def get_coin(symbol, color=None):
|
||||
if color is None:
|
||||
color = COIN_COLOR_MAP.get(symbol, GREY_E)
|
||||
coin = VGroup()
|
||||
circ = Circle()
|
||||
circ.set_fill(color, 1)
|
||||
circ.set_stroke(WHITE, 1)
|
||||
circ.set_height(1)
|
||||
label = TextMobject(symbol)
|
||||
label.set_height(0.5 * circ.get_height())
|
||||
label.move_to(circ)
|
||||
coin.add(circ, label)
|
||||
coin.symbol = symbol
|
||||
coin.lock_triangulation()
|
||||
return coin
|
||||
|
||||
|
||||
def get_random_coin(**kwargs):
|
||||
return get_random_process([get_coin("H"), get_coin("T")], **kwargs)
|
||||
|
||||
|
||||
def get_prob_coin_label(symbol="H", color=None, p=0.5, num_decimal_places=2):
|
||||
label = TexMobject("P", "(", "00", ")", "=",)
|
||||
coin = get_coin(symbol, color)
|
||||
template = label.get_part_by_tex("00")
|
||||
coin.replace(template)
|
||||
label.replace_submobject(label.index_of_part(template), coin)
|
||||
rhs = DecimalNumber(p, num_decimal_places=num_decimal_places)
|
||||
rhs.next_to(label, RIGHT, buff=MED_SMALL_BUFF)
|
||||
label.add(rhs)
|
||||
return label
|
||||
|
||||
|
||||
def get_q_box(mob):
|
||||
box = SurroundingRectangle(mob)
|
||||
box.set_stroke(WHITE, 1)
|
||||
box.set_fill(GREY_E, 1)
|
||||
q_marks = TexMobject("???")
|
||||
max_width = 0.8 * box.get_width()
|
||||
max_height = 0.8 * box.get_height()
|
||||
|
||||
if q_marks.get_width() > max_width:
|
||||
q_marks.set_width(max_width)
|
||||
|
||||
if q_marks.get_height() > max_height:
|
||||
q_marks.set_height(max_height)
|
||||
|
||||
q_marks.move_to(box)
|
||||
box.add(q_marks)
|
||||
return box
|
||||
|
||||
|
||||
def get_coin_grid(bools, height=6):
|
||||
coins = VGroup(*[
|
||||
get_coin("H" if heads else "T")
|
||||
for heads in bools
|
||||
])
|
||||
coins.arrange_in_grid()
|
||||
coins.set_height(height)
|
||||
return coins
|
||||
|
||||
|
||||
def get_prob_positive_experience_label(include_equals=False,
|
||||
include_decimal=False,
|
||||
include_q_mark=False):
|
||||
|
@ -325,11 +370,37 @@ def get_beta_dist_axes(y_max=20, y_unit=2, label_y=False, **kwargs):
|
|||
return result
|
||||
|
||||
|
||||
def scaled_pdf_axes(scale_factor=3.5):
|
||||
axes = get_beta_dist_axes(
|
||||
label_y=True,
|
||||
y_unit=1,
|
||||
)
|
||||
axes.y_axis.numbers.set_submobjects([
|
||||
*axes.y_axis.numbers[:5],
|
||||
*axes.y_axis.numbers[4::5]
|
||||
])
|
||||
sf = scale_factor
|
||||
axes.y_axis.stretch(sf, 1, about_point=axes.c2p(0, 0))
|
||||
for number in axes.y_axis.numbers:
|
||||
number.stretch(1 / sf, 1)
|
||||
axes.y_axis_label.to_edge(LEFT)
|
||||
axes.y_axis_label.add_background_rectangle(opacity=1)
|
||||
axes.set_stroke(background=True)
|
||||
return axes
|
||||
|
||||
|
||||
def close_off_graph(axes, graph):
|
||||
x_max = axes.x_axis.p2n(graph.get_end())
|
||||
graph.add_line_to(axes.c2p(x_max, 0))
|
||||
graph.add_line_to(axes.c2p(0, 0))
|
||||
graph.lock_triangulation()
|
||||
return graph
|
||||
|
||||
|
||||
def get_beta_graph(axes, n_plus, n_minus, **kwargs):
|
||||
dist = scipy.stats.beta(n_plus + 1, n_minus + 1)
|
||||
graph = axes.get_graph(dist.pdf, **kwargs)
|
||||
graph.add_line_to(axes.c2p(1, 0))
|
||||
graph.add_line_to(axes.c2p(0, 0))
|
||||
close_off_graph(axes, graph)
|
||||
graph.set_stroke(BLUE, 2)
|
||||
graph.set_fill(BLUE_E, 1)
|
||||
graph.lock_triangulation()
|
||||
|
|
754
from_3b1b/active/ctracing.py
Normal file
754
from_3b1b/active/ctracing.py
Normal file
|
@ -0,0 +1,754 @@
|
|||
from manimlib.imports import *
|
||||
from from_3b1b.active.sir import *
|
||||
|
||||
|
||||
class LastFewMonths(Scene):
|
||||
def construct(self):
|
||||
words = TextMobject("Last ", "few\\\\", "months:")
|
||||
words.set_height(4)
|
||||
underlines = VGroup()
|
||||
for word in words:
|
||||
underline = Line(LEFT, RIGHT)
|
||||
underline.match_width(word)
|
||||
underline.next_to(word, DOWN, SMALL_BUFF)
|
||||
underlines.add(underline)
|
||||
underlines[0].stretch(1.4, 0, about_edge=LEFT)
|
||||
underlines.set_color(BLUE)
|
||||
|
||||
# self.play(ShowCreation(underlines))
|
||||
self.play(ShowIncreasingSubsets(words, run_time=0.75, rate_func=linear))
|
||||
self.wait()
|
||||
|
||||
|
||||
class UnemploymentTitle(Scene):
|
||||
def construct(self):
|
||||
words = TextMobject("Unemployment claims\\\\per week in the US")[0]
|
||||
words.set_width(FRAME_WIDTH - 1)
|
||||
words.to_edge(UP)
|
||||
arrow = Arrow(
|
||||
words.get_bottom(),
|
||||
words.get_bottom() + 3 * RIGHT + 3 * DOWN,
|
||||
stroke_width=10,
|
||||
tip_length=0.5,
|
||||
)
|
||||
arrow.set_color(BLUE_E)
|
||||
words.set_color(BLACK)
|
||||
self.play(
|
||||
ShowIncreasingSubsets(words),
|
||||
ShowCreation(arrow),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class ExplainTracing(Scene):
|
||||
def construct(self):
|
||||
# Words
|
||||
words = VGroup(
|
||||
TextMobject("Testing, ", "Testing, ", "Testing!"),
|
||||
TextMobject("Contact Tracing"),
|
||||
)
|
||||
words[0].set_color(GREEN)
|
||||
words[1].set_color(BLUE_B)
|
||||
words.set_width(FRAME_WIDTH - 2)
|
||||
words.arrange(DOWN, buff=1)
|
||||
|
||||
self.play(ShowIncreasingSubsets(words[0], rate_func=linear))
|
||||
self.wait()
|
||||
self.play(Write(words[1], run_time=1))
|
||||
self.wait()
|
||||
|
||||
self.play(
|
||||
words[1].to_edge, UP,
|
||||
FadeOutAndShift(words[0], 6 * UP)
|
||||
)
|
||||
|
||||
ct_word = words[1][0]
|
||||
|
||||
# Groups
|
||||
clusters = VGroup()
|
||||
for x in range(4):
|
||||
cluster = VGroup()
|
||||
for y in range(4):
|
||||
cluster.add(Randolph())
|
||||
cluster.arrange_in_grid(buff=LARGE_BUFF)
|
||||
clusters.add(cluster)
|
||||
clusters.scale(0.5)
|
||||
clusters.arrange_in_grid(buff=2)
|
||||
clusters.set_height(4)
|
||||
|
||||
self.play(FadeIn(clusters))
|
||||
|
||||
pis = VGroup()
|
||||
boxes = VGroup()
|
||||
for cluster in clusters:
|
||||
for pi in cluster:
|
||||
pis.add(pi)
|
||||
box = SurroundingRectangle(pi, buff=0.05)
|
||||
boxes.add(box)
|
||||
pi.box = box
|
||||
|
||||
boxes.set_stroke(WHITE, 1)
|
||||
|
||||
sicky = clusters[0][2]
|
||||
covid_words = TextMobject("COVID-19\\\\Positive!")
|
||||
covid_words.set_color(RED)
|
||||
arrow = Vector(RIGHT, color=RED)
|
||||
arrow.next_to(sicky, LEFT)
|
||||
covid_words.next_to(arrow, LEFT, SMALL_BUFF)
|
||||
|
||||
self.play(
|
||||
sicky.change, "sick",
|
||||
sicky.set_color, "#9BBD37",
|
||||
FadeInFrom(covid_words, RIGHT),
|
||||
GrowArrow(arrow),
|
||||
)
|
||||
self.play(ShowCreation(sicky.box))
|
||||
self.wait(2)
|
||||
anims = []
|
||||
for pi in clusters[0]:
|
||||
if pi is not sicky:
|
||||
anims.append(ApplyMethod(pi.change, "tired"))
|
||||
anims.append(ShowCreation(pi.box))
|
||||
self.play(*anims)
|
||||
self.wait()
|
||||
|
||||
self.play(VFadeIn(
|
||||
boxes[4:],
|
||||
run_time=2,
|
||||
rate_func=there_and_back_with_pause,
|
||||
))
|
||||
self.wait()
|
||||
|
||||
self.play(FadeOut(
|
||||
VGroup(
|
||||
covid_words,
|
||||
arrow,
|
||||
*boxes[:4],
|
||||
*pis,
|
||||
),
|
||||
lag_ratio=0.1,
|
||||
run_time=3,
|
||||
))
|
||||
self.play(ct_word.move_to, 2 * UP)
|
||||
|
||||
# Underlines
|
||||
implies = TexMobject("\\Downarrow")
|
||||
implies.scale(2)
|
||||
implies.next_to(ct_word, DOWN, MED_LARGE_BUFF)
|
||||
loc_tracking = TextMobject("Location Tracking")
|
||||
loc_tracking.set_color(GREY_BROWN)
|
||||
loc_tracking.match_height(ct_word)
|
||||
loc_tracking.next_to(implies, DOWN, MED_LARGE_BUFF)
|
||||
|
||||
q_marks = TexMobject("???")
|
||||
q_marks.scale(2)
|
||||
q_marks.next_to(implies, RIGHT)
|
||||
|
||||
cross = Cross(implies)
|
||||
cross.set_stroke(RED, 7)
|
||||
|
||||
self.play(
|
||||
Write(implies),
|
||||
FadeInFrom(loc_tracking, UP)
|
||||
)
|
||||
self.play(FadeIn(q_marks, lag_ratio=0.1))
|
||||
self.wait()
|
||||
|
||||
parts = VGroup(ct_word[:7], ct_word[7:])
|
||||
lines = VGroup()
|
||||
for part in parts:
|
||||
line = Line(part.get_left(), part.get_right())
|
||||
line.align_to(part[0], DOWN)
|
||||
line.shift(0.1 * DOWN)
|
||||
lines.add(line)
|
||||
|
||||
ct_word.set_stroke(BLACK, 2, background=True)
|
||||
self.add(lines[1], ct_word)
|
||||
self.play(ShowCreation(lines[1]))
|
||||
self.wait()
|
||||
self.play(ShowCreation(lines[0]))
|
||||
self.wait()
|
||||
|
||||
self.play(
|
||||
ShowCreation(cross),
|
||||
FadeOutAndShift(q_marks, RIGHT),
|
||||
FadeOut(lines),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
dp_3t = TextMobject("DP-3T")
|
||||
dp_3t.match_height(ct_word)
|
||||
dp_3t.move_to(loc_tracking)
|
||||
dp_3t_long = TextMobject("Decentralized Privacy-Preserving Proximity Tracing")
|
||||
dp_3t_long.next_to(dp_3t, DOWN, LARGE_BUFF)
|
||||
|
||||
arrow = Vector(UP)
|
||||
arrow.set_stroke(width=8)
|
||||
arrow.move_to(implies)
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(dp_3t),
|
||||
FadeOut(loc_tracking),
|
||||
FadeOut(implies),
|
||||
FadeOut(cross),
|
||||
ShowCreation(arrow)
|
||||
)
|
||||
self.play(Write(dp_3t_long))
|
||||
self.wait()
|
||||
|
||||
|
||||
class ContactTracingMisnomer(Scene):
|
||||
def construct(self):
|
||||
# Word play
|
||||
words = TextMobject("Contact ", "Tracing")
|
||||
words.scale(2)
|
||||
rects = VGroup(*[
|
||||
SurroundingRectangle(word, buff=0.2)
|
||||
for word in words
|
||||
])
|
||||
expl1 = TextMobject("Doesn't ``trace'' you...")
|
||||
expl2 = TextMobject("...or your contacts")
|
||||
expls = VGroup(expl1, expl2)
|
||||
colors = [RED, BLUE]
|
||||
|
||||
self.add(words)
|
||||
for vect, rect, expl, color in zip([UP, DOWN], reversed(rects), expls, colors):
|
||||
arrow = Vector(-vect)
|
||||
arrow.next_to(rect, vect, SMALL_BUFF)
|
||||
expl.next_to(arrow, vect, SMALL_BUFF)
|
||||
rect.set_color(color)
|
||||
arrow.set_color(color)
|
||||
expl.set_color(color)
|
||||
|
||||
self.play(
|
||||
FadeInFrom(expl, -vect),
|
||||
GrowArrow(arrow),
|
||||
ShowCreation(rect),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
self.play(Write(
|
||||
VGroup(*self.mobjects),
|
||||
rate_func=lambda t: smooth(1 - t),
|
||||
run_time=3,
|
||||
))
|
||||
|
||||
|
||||
class ContactTracingWords(Scene):
|
||||
def construct(self):
|
||||
words = TextMobject("Contact\\\\", "Tracing")
|
||||
words.set_height(4)
|
||||
for word in words:
|
||||
self.add(word)
|
||||
self.wait()
|
||||
self.wait()
|
||||
return
|
||||
self.play(ShowIncreasingSubsets(words))
|
||||
self.wait()
|
||||
self.play(
|
||||
words.set_height, 1,
|
||||
words.to_corner, UL,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class WanderingDotsWithLines(Scene):
|
||||
def construct(self):
|
||||
sim = SIRSimulation(
|
||||
city_population=20,
|
||||
person_type=DotPerson,
|
||||
person_config={
|
||||
"color_map": {
|
||||
"S": GREY,
|
||||
"I": GREY,
|
||||
"R": GREY,
|
||||
},
|
||||
"infection_ring_style": {
|
||||
"stroke_color": YELLOW,
|
||||
},
|
||||
"max_speed": 0.5,
|
||||
},
|
||||
infection_time=100,
|
||||
)
|
||||
|
||||
for person in sim.people:
|
||||
person.set_status("S")
|
||||
person.infection_start_time += random.random()
|
||||
|
||||
lines = VGroup()
|
||||
|
||||
max_dist = 1.25
|
||||
|
||||
def update_lines(lines):
|
||||
lines.remove(*lines.submobjects)
|
||||
for p1 in sim.people:
|
||||
for p2 in sim.people:
|
||||
if p1 is p2:
|
||||
continue
|
||||
dist = get_norm(p1.get_center() - p2.get_center())
|
||||
if dist < max_dist:
|
||||
line = Line(p1.get_center(), p2.get_center())
|
||||
alpha = (max_dist - dist) / max_dist
|
||||
line.set_stroke(
|
||||
interpolate_color(WHITE, RED, alpha),
|
||||
width=4 * alpha
|
||||
)
|
||||
lines.add(line)
|
||||
|
||||
lines.add_updater(update_lines)
|
||||
|
||||
self.add(lines)
|
||||
self.add(sim)
|
||||
self.wait(10)
|
||||
for person in sim.people:
|
||||
person.set_status("I")
|
||||
person.infection_start_time += random.random()
|
||||
self.wait(50)
|
||||
|
||||
|
||||
class WhatAboutPeopleWithoutPhones(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.student_says(
|
||||
"What about people\\\\without phones?",
|
||||
target_mode="sassy",
|
||||
added_anims=[self.teacher.change, "guilty"]
|
||||
)
|
||||
self.change_student_modes("angry", "angry", "sassy")
|
||||
self.wait()
|
||||
self.play(self.teacher.change, "tease")
|
||||
self.wait()
|
||||
|
||||
words = VectorizedPoint()
|
||||
words.scale(1.5)
|
||||
words.to_corner(UL)
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(words),
|
||||
RemovePiCreatureBubble(self.students[2]),
|
||||
*[
|
||||
ApplyMethod(pi.change, "pondering", words)
|
||||
for pi in self.pi_creatures
|
||||
]
|
||||
)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class PiGesture1(Scene):
|
||||
def construct(self):
|
||||
randy = Randolph(mode="raise_right_hand", height=2)
|
||||
bubble = randy.get_bubble(
|
||||
bubble_class=SpeechBubble,
|
||||
height=2, width=3,
|
||||
)
|
||||
bubble.write("This one's\\\\great")
|
||||
bubble.content.scale(0.8)
|
||||
bubble.content.set_color(BLACK)
|
||||
bubble.set_color(BLACK)
|
||||
bubble.set_fill(opacity=0)
|
||||
randy.set_stroke(BLACK, 5, background=True)
|
||||
self.add(randy, bubble, bubble.content)
|
||||
|
||||
|
||||
class PiGesture2(Scene):
|
||||
def construct(self):
|
||||
randy = Randolph(mode="raise_left_hand", height=2)
|
||||
randy.look(UL)
|
||||
# randy.flip()
|
||||
randy.set_color(GREY_BROWN)
|
||||
bubble = randy.get_bubble(
|
||||
bubble_class=SpeechBubble,
|
||||
height=2, width=3,
|
||||
direction=LEFT,
|
||||
)
|
||||
bubble.write("So is\\\\this one")
|
||||
bubble.content.scale(0.8)
|
||||
bubble.content.set_color(BLACK)
|
||||
bubble.set_color(BLACK)
|
||||
bubble.set_fill(opacity=0)
|
||||
randy.set_stroke(BLACK, 5, background=True)
|
||||
self.add(randy, bubble, bubble.content)
|
||||
|
||||
|
||||
class PiGesture3(Scene):
|
||||
def construct(self):
|
||||
randy = Randolph(mode="hooray", height=2)
|
||||
randy.flip()
|
||||
bubble = randy.get_bubble(
|
||||
bubble_class=SpeechBubble,
|
||||
height=2, width=3,
|
||||
direction=LEFT,
|
||||
)
|
||||
bubble.write("And this\\\\one")
|
||||
bubble.content.scale(0.8)
|
||||
bubble.content.set_color(BLACK)
|
||||
bubble.set_color(BLACK)
|
||||
bubble.set_fill(opacity=0)
|
||||
randy.set_stroke(BLACK, 5, background=True)
|
||||
self.add(randy, bubble, bubble.content)
|
||||
|
||||
|
||||
class AppleGoogleCoop(Scene):
|
||||
def construct(self):
|
||||
logos = Group(
|
||||
self.get_apple_logo(),
|
||||
self.get_google_logo(),
|
||||
)
|
||||
for logo in logos:
|
||||
logo.set_height(2)
|
||||
apple, google = logos
|
||||
|
||||
logos.arrange(RIGHT, buff=3)
|
||||
|
||||
arrows = VGroup()
|
||||
for vect, u in zip([UP, DOWN], [0, 1]):
|
||||
m1, m2 = logos[u], logos[1 - u]
|
||||
arrows.add(Arrow(
|
||||
m1.get_edge_center(vect),
|
||||
m2.get_edge_center(vect),
|
||||
path_arc=-90 * DEGREES,
|
||||
buff=MED_LARGE_BUFF,
|
||||
stroke_width=10,
|
||||
))
|
||||
|
||||
self.play(LaggedStart(
|
||||
Write(apple),
|
||||
FadeIn(google),
|
||||
lag_ratio=0.7,
|
||||
))
|
||||
self.wait()
|
||||
self.play(ShowCreation(arrows, run_time=2))
|
||||
self.wait()
|
||||
|
||||
def get_apple_logo(self):
|
||||
result = SVGMobject("apple_logo")
|
||||
result.set_color("#b3b3b3")
|
||||
return result
|
||||
|
||||
def get_google_logo(self):
|
||||
result = ImageMobject("google_logo_black")
|
||||
return result
|
||||
|
||||
|
||||
class LocationTracking(Scene):
|
||||
def construct(self):
|
||||
question = TextMobject(
|
||||
"Would you like this company to track\\\\",
|
||||
"and occasionally sell your location?"
|
||||
)
|
||||
question.to_edge(UP, buff=LARGE_BUFF)
|
||||
|
||||
slider = Rectangle(width=1.25, height=0.5)
|
||||
slider.round_corners(radius=0.25)
|
||||
slider.set_fill(GREEN, 1)
|
||||
slider.next_to(question, DOWN, buff=MED_LARGE_BUFF)
|
||||
|
||||
dot = Dot(radius=0.25)
|
||||
dot.set_fill(GREY_C, 1)
|
||||
dot.set_stroke(WHITE, 3)
|
||||
dot.move_to(slider, RIGHT)
|
||||
|
||||
morty = Mortimer()
|
||||
morty.next_to(slider, RIGHT)
|
||||
morty.to_edge(DOWN)
|
||||
|
||||
bubble = morty.get_bubble(
|
||||
height=2,
|
||||
width=3,
|
||||
direction=LEFT,
|
||||
)
|
||||
|
||||
answer = TextMobject("Um...", "no.")
|
||||
answer.set_height(0.4)
|
||||
answer.set_color(YELLOW)
|
||||
bubble.add_content(answer)
|
||||
|
||||
self.add(morty)
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(question),
|
||||
Write(slider),
|
||||
FadeIn(dot),
|
||||
)
|
||||
self.play(morty.change, "confused", slider)
|
||||
self.play(Blink(morty))
|
||||
self.play(
|
||||
FadeIn(bubble),
|
||||
Write(answer[0]),
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
dot.move_to, slider, LEFT,
|
||||
slider.set_fill, {"opacity": 0},
|
||||
FadeIn(answer[1]),
|
||||
morty.change, "sassy"
|
||||
)
|
||||
self.play(Blink(morty))
|
||||
self.wait(2)
|
||||
self.play(Blink(morty))
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class MoreLinks(Scene):
|
||||
def construct(self):
|
||||
words = TextMobject("See more links\\\\in the description.")
|
||||
words.scale(2)
|
||||
words.to_edge(UP, buff=2)
|
||||
arrows = VGroup(*[
|
||||
Vector(1.5 * DOWN, stroke_width=10)
|
||||
for x in range(4)
|
||||
])
|
||||
arrows.arrange(RIGHT, buff=0.75)
|
||||
arrows.next_to(words, DOWN, buff=0.5)
|
||||
for arrow, color in zip(arrows, [BLUE_D, BLUE_C, BLUE_E, GREY_BROWN]):
|
||||
arrow.set_color(color)
|
||||
self.play(Write(words))
|
||||
self.play(LaggedStartMap(ShowCreation, arrows))
|
||||
self.wait()
|
||||
|
||||
|
||||
class LDMEndScreen(PatreonEndScreen):
|
||||
CONFIG = {
|
||||
"scroll_time": 20,
|
||||
"specific_patrons": [
|
||||
"1stViewMaths",
|
||||
"Aaron",
|
||||
"Adam Dřínek",
|
||||
"Adam Margulies",
|
||||
"Aidan Shenkman",
|
||||
"Alan Stein",
|
||||
"Albin Egasse",
|
||||
"Alex Mijalis",
|
||||
"Alexander Mai",
|
||||
"Alexis Olson",
|
||||
"Ali Yahya",
|
||||
"Andreas Snekloth Kongsgaard",
|
||||
"Andrew Busey",
|
||||
"Andrew Cary",
|
||||
"Andrew R. Whalley",
|
||||
"Aravind C V",
|
||||
"Arjun Chakroborty",
|
||||
"Arthur Zey",
|
||||
"Ashwin Siddarth",
|
||||
"Augustine Lim",
|
||||
"Austin Goodman",
|
||||
"Avi Finkel",
|
||||
"Awoo",
|
||||
"Axel Ericsson",
|
||||
"Ayan Doss",
|
||||
"AZsorcerer",
|
||||
"Barry Fam",
|
||||
"Bartosz Burclaf",
|
||||
"Ben Delo",
|
||||
"Benjamin Bailey",
|
||||
"Bernd Sing",
|
||||
"Bill Gatliff",
|
||||
"Boris Veselinovich",
|
||||
"Bradley Pirtle",
|
||||
"Brandon Huang",
|
||||
"Brendan Shah",
|
||||
"Brian Cloutier",
|
||||
"Brian Staroselsky",
|
||||
"Britt Selvitelle",
|
||||
"Britton Finley",
|
||||
"Burt Humburg",
|
||||
"Calvin Lin",
|
||||
"Carl-Johan R. Nordangård",
|
||||
"Charles Southerland",
|
||||
"Charlie N",
|
||||
"Chris Connett",
|
||||
"Chris Druta",
|
||||
"Christian Kaiser",
|
||||
"cinterloper",
|
||||
"Clark Gaebel",
|
||||
"Colwyn Fritze-Moor",
|
||||
"Corey Ogburn",
|
||||
"D. Sivakumar",
|
||||
"Dan Herbatschek",
|
||||
"Daniel Brown",
|
||||
"Daniel Herrera C",
|
||||
"Darrell Thomas",
|
||||
"Dave B",
|
||||
"Dave Cole",
|
||||
"Dave Kester",
|
||||
"dave nicponski",
|
||||
"David B. Hill",
|
||||
"David Clark",
|
||||
"David Gow",
|
||||
"Delton Ding",
|
||||
"Dominik Wagner",
|
||||
"Eduardo Rodriguez",
|
||||
"Emilio Mendoza",
|
||||
"emptymachine",
|
||||
"Eric Younge",
|
||||
"Eryq Ouithaqueue",
|
||||
"Federico Lebron",
|
||||
"Fernando Via Canel",
|
||||
"Frank R. Brown, Jr.",
|
||||
"gary",
|
||||
"Giovanni Filippi",
|
||||
"Goodwine",
|
||||
"Hal Hildebrand",
|
||||
"Heptonion",
|
||||
"Hitoshi Yamauchi",
|
||||
"Isaac Gubernick",
|
||||
"Ivan Sorokin",
|
||||
"Jacob Baxter",
|
||||
"Jacob Harmon",
|
||||
"Jacob Hartmann",
|
||||
"Jacob Magnuson",
|
||||
"Jalex Stark",
|
||||
"Jameel Syed",
|
||||
"James Beall",
|
||||
"Jason Hise",
|
||||
"Jayne Gabriele",
|
||||
"Jean-Manuel Izaret",
|
||||
"Jeff Dodds",
|
||||
"Jeff Linse",
|
||||
"Jeff Straathof",
|
||||
"Jeffrey Wolberg",
|
||||
"Jimmy Yang",
|
||||
"Joe Pregracke",
|
||||
"Johan Auster",
|
||||
"John C. Vesey",
|
||||
"John Camp",
|
||||
"John Haley",
|
||||
"John Le",
|
||||
"John Luttig",
|
||||
"John Rizzo",
|
||||
"John V Wertheim",
|
||||
"jonas.app",
|
||||
"Jonathan Heckerman",
|
||||
"Jonathan Wilson",
|
||||
"Joseph John Cox",
|
||||
"Joseph Kelly",
|
||||
"Josh Kinnear",
|
||||
"Joshua Claeys",
|
||||
"Joshua Ouellette",
|
||||
"Juan Benet",
|
||||
"Julien Dubois",
|
||||
"Kai-Siang Ang",
|
||||
"Kanan Gill",
|
||||
"Karl Niu",
|
||||
"Kartik Cating-Subramanian",
|
||||
"Kaustuv DeBiswas",
|
||||
"Killian McGuinness",
|
||||
"kkm",
|
||||
"Klaas Moerman",
|
||||
"Kristoffer Börebäck",
|
||||
"Kros Dai",
|
||||
"L0j1k",
|
||||
"Lael S Costa",
|
||||
"LAI Oscar",
|
||||
"Lambda GPU Workstations",
|
||||
"Laura Gast",
|
||||
"Lee Redden",
|
||||
"Linh Tran",
|
||||
"Luc Ritchie",
|
||||
"Ludwig Schubert",
|
||||
"Lukas Biewald",
|
||||
"Lukas Zenick",
|
||||
"Magister Mugit",
|
||||
"Magnus Dahlström",
|
||||
"Magnus Hiie",
|
||||
"Manoj Rewatkar - RITEK SOLUTIONS",
|
||||
"Mark B Bahu",
|
||||
"Mark Heising",
|
||||
"Mark Hopkins",
|
||||
"Mark Mann",
|
||||
"Martin Price",
|
||||
"Mathias Jansson",
|
||||
"Matt Godbolt",
|
||||
"Matt Langford",
|
||||
"Matt Roveto",
|
||||
"Matt Russell",
|
||||
"Matteo Delabre",
|
||||
"Matthew Bouchard",
|
||||
"Matthew Cocke",
|
||||
"Maxim Nitsche",
|
||||
"Michael Bos",
|
||||
"Michael Hardel",
|
||||
"Michael W White",
|
||||
"Mirik Gogri",
|
||||
"Molly Mackinlay",
|
||||
"Mustafa Mahdi",
|
||||
"Márton Vaitkus",
|
||||
"Nero Li",
|
||||
"Nicholas Cahill",
|
||||
"Nikita Lesnikov",
|
||||
"Nitu Kitchloo",
|
||||
"Oleg Leonov",
|
||||
"Oliver Steele",
|
||||
"Omar Zrien",
|
||||
"Omer Tuchfeld",
|
||||
"Patrick Gibson",
|
||||
"Patrick Lucas",
|
||||
"Pavel Dubov",
|
||||
"Pesho Ivanov",
|
||||
"Petar Veličković",
|
||||
"Peter Ehrnstrom",
|
||||
"Peter Francis",
|
||||
"Peter Mcinerney",
|
||||
"Pierre Lancien",
|
||||
"Pradeep Gollakota",
|
||||
"Rafael Bove Barrios",
|
||||
"Raghavendra Kotikalapudi",
|
||||
"Randy C. Will",
|
||||
"rehmi post",
|
||||
"Rex Godby",
|
||||
"Ripta Pasay",
|
||||
"Rish Kundalia",
|
||||
"Roman Sergeychik",
|
||||
"Roobie",
|
||||
"Ryan Atallah",
|
||||
"Samuel Judge",
|
||||
"SansWord Huang",
|
||||
"Scott Gray",
|
||||
"Scott Walter, Ph.D.",
|
||||
"soekul",
|
||||
"Solara570",
|
||||
"Spyridon Michalakis",
|
||||
"Stephen Shanahan",
|
||||
"Steve Huynh",
|
||||
"Steve Muench",
|
||||
"Steve Sperandeo",
|
||||
"Steven Siddals",
|
||||
"Stevie Metke",
|
||||
"Sundar Subbarayan",
|
||||
"supershabam",
|
||||
"Suteerth Vishnu",
|
||||
"Suthen Thomas",
|
||||
"Tal Einav",
|
||||
"Taras Bobrovytsky",
|
||||
"Tauba Auerbach",
|
||||
"Ted Suzman",
|
||||
"Terry Hayes",
|
||||
"THIS IS THE point OF NO RE tUUurRrhghgGHhhnnn",
|
||||
"Thomas J Sargent",
|
||||
"Thomas Tarler",
|
||||
"Tianyu Ge",
|
||||
"Tihan Seale",
|
||||
"Tim Erbes",
|
||||
"Tim Kazik",
|
||||
"Tomasz Legutko",
|
||||
"Tyler Herrmann",
|
||||
"Tyler Parcell",
|
||||
"Tyler VanValkenburg",
|
||||
"Tyler Veness",
|
||||
"Ubiquity Ventures",
|
||||
"Vassili Philippov",
|
||||
"Vasu Dubey",
|
||||
"Veritasium",
|
||||
"Vignesh Ganapathi Subramanian",
|
||||
"Vinicius Reis",
|
||||
"Vladimir Solomatin",
|
||||
"Wooyong Ee",
|
||||
"Xuanji Li",
|
||||
"Yana Chernobilsky",
|
||||
"Yavor Ivanov",
|
||||
"Yetinother",
|
||||
"YinYangBalance.Asia",
|
||||
"Yu Jun",
|
||||
"Yurii Monastyrshyn",
|
||||
"Zachariah Rosenberg",
|
||||
],
|
||||
}
|
|
@ -331,6 +331,8 @@ class FourierOfPiSymbol(FourierCirclesScene):
|
|||
def add_vectors_circles_path(self):
|
||||
path = self.get_path()
|
||||
coefs = self.get_coefficients_of_path(path)
|
||||
for coef in coefs:
|
||||
print(coef)
|
||||
vectors = self.get_rotating_vectors(coefficients=coefs)
|
||||
circles = self.get_circles(vectors)
|
||||
self.set_decreasing_stroke_widths(circles)
|
||||
|
|
|
@ -1369,9 +1369,7 @@ class SineWaveScaledByExp(TemperatureGraphScene):
|
|||
theta=-80 * DEGREES,
|
||||
distance=50,
|
||||
)
|
||||
self.camera.set_frame_center(
|
||||
2 * RIGHT,
|
||||
)
|
||||
self.camera.frame.move_to(2 * RIGHT)
|
||||
|
||||
def show_sine_wave(self):
|
||||
time_tracker = ValueTracker(0)
|
||||
|
|
1445
from_3b1b/active/ldm.py
Normal file
1445
from_3b1b/active/ldm.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,3 @@
|
|||
from functools import reduce
|
||||
import operator as op
|
||||
import moderngl
|
||||
from colour import Color
|
||||
|
||||
|
@ -9,39 +7,127 @@ import itertools as it
|
|||
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.mobject import Point
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.iterables import batch_by_property
|
||||
from manimlib.utils.simple_functions import fdiv
|
||||
from manimlib.utils.shaders import shader_info_to_id
|
||||
from manimlib.utils.shaders import shader_id_to_info
|
||||
from manimlib.utils.shaders import get_shader_code_from_file
|
||||
from manimlib.utils.simple_functions import clip
|
||||
from manimlib.utils.space_ops import angle_of_vector
|
||||
from manimlib.utils.space_ops import rotation_matrix_transpose_from_quaternion
|
||||
from manimlib.utils.space_ops import rotation_matrix_transpose
|
||||
from manimlib.utils.space_ops import quaternion_from_angle_axis
|
||||
from manimlib.utils.space_ops import quaternion_mult
|
||||
|
||||
|
||||
# TODO, think about how to incorporate perspective,
|
||||
# and change get_height, etc. to take orientation into account
|
||||
class CameraFrame(Mobject):
|
||||
CONFIG = {
|
||||
"width": FRAME_WIDTH,
|
||||
"height": FRAME_HEIGHT,
|
||||
"center": ORIGIN,
|
||||
"center_point": ORIGIN,
|
||||
# Theta, phi, gamma
|
||||
"euler_angles": [0, 0, 0],
|
||||
"focal_distance": 5,
|
||||
}
|
||||
|
||||
def init_points(self):
|
||||
self.points = np.array([UL, UR, DR, DL])
|
||||
self.set_width(self.width, stretch=True)
|
||||
self.set_height(self.height, stretch=True)
|
||||
self.move_to(self.center)
|
||||
self.save_state()
|
||||
self.points = np.array([self.center_point])
|
||||
self.euler_angles = np.array(self.euler_angles, dtype='float64')
|
||||
|
||||
def to_default_state(self):
|
||||
self.center()
|
||||
self.set_height(FRAME_HEIGHT)
|
||||
self.set_width(FRAME_WIDTH)
|
||||
self.set_rotation(0, 0, 0)
|
||||
return self
|
||||
|
||||
def get_inverse_camera_position_matrix(self):
|
||||
result = np.identity(4)
|
||||
# First shift so that origin of real space coincides with camera origin
|
||||
result[:3, 3] = -self.get_center().T
|
||||
# Rotate based on camera orientation
|
||||
result[:3, :3] = np.dot(self.get_inverse_camera_rotation_matrix(), result[:3, :3])
|
||||
# Scale to have height 2 (matching the height of the box [-1, 1]^2)
|
||||
result *= 2 / self.height
|
||||
return result
|
||||
|
||||
def get_inverse_camera_rotation_matrix(self):
|
||||
theta, phi, gamma = self.euler_angles
|
||||
quat = quaternion_mult(
|
||||
quaternion_from_angle_axis(theta, OUT),
|
||||
quaternion_from_angle_axis(phi, RIGHT),
|
||||
quaternion_from_angle_axis(gamma, OUT),
|
||||
)
|
||||
return rotation_matrix_transpose_from_quaternion(quat)
|
||||
|
||||
def rotate(self, angle, axis=OUT, **kwargs):
|
||||
curr_rot_T = self.get_inverse_camera_rotation_matrix()
|
||||
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])
|
||||
theta = angle_of_vector(Fz[:2]) + PI / 2
|
||||
partial_rot_T = np.dot(
|
||||
rotation_matrix_transpose(phi, RIGHT),
|
||||
rotation_matrix_transpose(theta, OUT),
|
||||
)
|
||||
gamma = angle_of_vector(np.dot(partial_rot_T, new_rot_T.T)[:, 0])
|
||||
# TODO, write a function that converts quaternions to euler angles
|
||||
self.euler_angles[:] = theta, phi, gamma
|
||||
return self
|
||||
|
||||
def set_rotation(self, theta=0, phi=0, gamma=0):
|
||||
self.euler_angles[:] = theta, phi, gamma
|
||||
return self
|
||||
|
||||
def increment_theta(self, dtheta):
|
||||
self.euler_angles[0] += dtheta
|
||||
return self
|
||||
|
||||
def increment_phi(self, dphi):
|
||||
self.euler_angles[1] = clip(self.euler_angles[1] + dphi, 0, PI)
|
||||
return self
|
||||
|
||||
def increment_gamma(self, dgamma):
|
||||
self.euler_angles[2] += dgamma
|
||||
return self
|
||||
|
||||
def scale(self, scale_factor, **kwargs):
|
||||
# TODO, handle about_point and about_edge?
|
||||
self.height *= scale_factor
|
||||
self.width *= scale_factor
|
||||
return self
|
||||
|
||||
def set_height(self, height):
|
||||
self.height = height
|
||||
return self
|
||||
|
||||
def set_width(self, width):
|
||||
self.width = width
|
||||
return self
|
||||
|
||||
def get_height(self):
|
||||
return self.height
|
||||
|
||||
def get_width(self):
|
||||
return self.width
|
||||
|
||||
def get_center(self):
|
||||
return self.points[0]
|
||||
|
||||
def get_focal_distance(self):
|
||||
return self.focal_distance
|
||||
|
||||
def interpolate(self, mobject1, mobject2, alpha, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class Camera(object):
|
||||
CONFIG = {
|
||||
"background_image": None,
|
||||
"frame_config": {
|
||||
"width": FRAME_WIDTH,
|
||||
"height": FRAME_HEIGHT,
|
||||
"center": ORIGIN,
|
||||
},
|
||||
"frame_config": {},
|
||||
"pixel_height": DEFAULT_PIXEL_HEIGHT,
|
||||
"pixel_width": DEFAULT_PIXEL_WIDTH,
|
||||
"frame_rate": DEFAULT_FRAME_RATE, # TODO, move this elsewhere
|
||||
|
@ -55,7 +141,8 @@ class Camera(object):
|
|||
"image_mode": "RGBA",
|
||||
"n_channels": 4,
|
||||
"pixel_array_dtype": 'uint8',
|
||||
"line_width_multiple": 0.01,
|
||||
"light_source_position": [-10, 10, 10], # TODO, add multiple light sources
|
||||
"apply_depth_test": False,
|
||||
}
|
||||
|
||||
def __init__(self, ctx=None, **kwargs):
|
||||
|
@ -65,6 +152,7 @@ class Camera(object):
|
|||
self.init_context(ctx)
|
||||
self.init_shaders()
|
||||
self.init_textures()
|
||||
self.init_light_source()
|
||||
|
||||
def init_frame(self):
|
||||
self.frame = CameraFrame(**self.frame_config)
|
||||
|
@ -78,13 +166,19 @@ class Camera(object):
|
|||
self.fbo = self.get_fbo()
|
||||
self.fbo.use()
|
||||
|
||||
self.ctx.enable(moderngl.BLEND)
|
||||
self.ctx.blend_func = (
|
||||
flag = moderngl.BLEND
|
||||
if self.apply_depth_test:
|
||||
flag |= moderngl.DEPTH_TEST
|
||||
self.ctx.enable(flag)
|
||||
self.ctx.blend_func = ( # Needed?
|
||||
moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA,
|
||||
moderngl.ONE, moderngl.ONE
|
||||
)
|
||||
self.background_fbo = None
|
||||
|
||||
def init_light_source(self):
|
||||
self.light_source = Point(self.light_source_position)
|
||||
|
||||
# Methods associated with the frame buffer
|
||||
def get_fbo(self):
|
||||
return self.ctx.simple_framebuffer(
|
||||
|
@ -107,8 +201,8 @@ class Camera(object):
|
|||
frame_height = frame_width / aspect_ratio
|
||||
else:
|
||||
frame_width = aspect_ratio * frame_height
|
||||
self.set_frame_height(frame_height)
|
||||
self.set_frame_width(frame_width)
|
||||
self.frame.set_height(frame_height)
|
||||
self.frame.set_width(frame_width)
|
||||
|
||||
def clear(self):
|
||||
rgba = (*Color(self.background_color).get_rgb(), self.background_opacity)
|
||||
|
@ -163,7 +257,6 @@ class Camera(object):
|
|||
def get_pixel_height(self):
|
||||
return self.get_pixel_shape()[1]
|
||||
|
||||
# TODO, make these work for a rotated frame
|
||||
def get_frame_height(self):
|
||||
return self.frame.get_height()
|
||||
|
||||
|
@ -176,17 +269,12 @@ class Camera(object):
|
|||
def get_frame_center(self):
|
||||
return self.frame.get_center()
|
||||
|
||||
def set_frame_height(self, height):
|
||||
self.frame.set_height(height, stretch=True)
|
||||
|
||||
def set_frame_width(self, width):
|
||||
self.frame.set_width(width, stretch=True)
|
||||
|
||||
def set_frame_center(self, center):
|
||||
self.frame.move_to(center)
|
||||
|
||||
def pixel_coords_to_space_coords(self, px, py, relative=False):
|
||||
pw, ph = self.fbo.size
|
||||
# pw, ph = self.fbo.size
|
||||
# Bad hack, not sure why this is needed.
|
||||
pw, ph = self.get_pixel_shape()
|
||||
pw //= 2
|
||||
ph //= 2
|
||||
fw, fh = self.get_frame_shape()
|
||||
fc = self.get_frame_center()
|
||||
if relative:
|
||||
|
@ -196,19 +284,6 @@ class Camera(object):
|
|||
scale = fh / ph
|
||||
return fc + scale * np.array([(px - pw / 2), (py - ph / 2), 0])
|
||||
|
||||
# TODO, account for 3d
|
||||
# Also, move this to CameraFrame?
|
||||
def is_in_frame(self, mobject):
|
||||
fc = self.get_frame_center()
|
||||
fh = self.get_frame_height()
|
||||
fw = self.get_frame_width()
|
||||
return not reduce(op.or_, [
|
||||
mobject.get_right()[0] < fc[0] - fw,
|
||||
mobject.get_bottom()[1] > fc[1] + fh,
|
||||
mobject.get_left()[0] > fc[0] + fw,
|
||||
mobject.get_top()[1] < fc[1] - fh,
|
||||
])
|
||||
|
||||
# Rendering
|
||||
def capture(self, *mobjects, **kwargs):
|
||||
self.refresh_shader_uniforms()
|
||||
|
@ -266,15 +341,17 @@ class Camera(object):
|
|||
if shader is None:
|
||||
return
|
||||
# TODO, think about how uniforms come from mobjects as well.
|
||||
fh = self.get_frame_height()
|
||||
fc = self.get_frame_center()
|
||||
pw, ph = self.get_pixel_shape()
|
||||
|
||||
transform = self.frame.get_inverse_camera_position_matrix()
|
||||
light = self.light_source.get_location()
|
||||
transformed_light = np.dot(transform, [*light, 1])[:3]
|
||||
mapping = {
|
||||
'scale': fh / 2, # Scale based on frame size
|
||||
'to_screen_space': tuple(transform.T.flatten()),
|
||||
'aspect_ratio': (pw / ph), # AR based on pixel shape
|
||||
'anti_alias_width': ANTI_ALIAS_WIDTH_OVER_FRAME_HEIGHT * fh,
|
||||
'frame_center': tuple(fc),
|
||||
'focal_distance': self.frame.get_focal_distance(),
|
||||
'anti_alias_width': 3 / ph, # 1.5 Pixel widths
|
||||
'light_source_position': tuple(transformed_light),
|
||||
}
|
||||
for key, value in mapping.items():
|
||||
try:
|
||||
|
@ -302,3 +379,8 @@ class Camera(object):
|
|||
texture.use(location=tid)
|
||||
self.path_to_texture_id[path] = tid
|
||||
return self.path_to_texture_id[path]
|
||||
|
||||
|
||||
class ThreeDCamera(Camera):
|
||||
# Purely here to keep old scenes happy
|
||||
pass
|
||||
|
|
|
@ -1,232 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from manimlib.camera.camera import Camera
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.three_d_utils import get_3d_vmob_end_corner
|
||||
from manimlib.mobject.three_d_utils import get_3d_vmob_end_corner_unit_normal
|
||||
from manimlib.mobject.three_d_utils import get_3d_vmob_start_corner
|
||||
from manimlib.mobject.three_d_utils import get_3d_vmob_start_corner_unit_normal
|
||||
from manimlib.mobject.types.point_cloud_mobject import Point
|
||||
from manimlib.mobject.value_tracker import ValueTracker
|
||||
from manimlib.utils.color import get_shaded_rgb
|
||||
from manimlib.utils.simple_functions import clip_in_place
|
||||
from manimlib.utils.space_ops import rotation_about_z
|
||||
from manimlib.utils.space_ops import rotation_matrix
|
||||
|
||||
|
||||
class ThreeDCamera(Camera):
|
||||
CONFIG = {
|
||||
"shading_factor": 0.2,
|
||||
"distance": 20.0,
|
||||
"default_distance": 5.0,
|
||||
"phi": 0, # Angle off z axis
|
||||
"theta": -90 * DEGREES, # Rotation about z axis
|
||||
"gamma": 0, # Rotation about normal vector to camera
|
||||
"light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT,
|
||||
"frame_center": ORIGIN,
|
||||
"should_apply_shading": True,
|
||||
"exponential_projection": False,
|
||||
"max_allowable_norm": 3 * FRAME_WIDTH,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Camera.__init__(self, *args, **kwargs)
|
||||
self.phi_tracker = ValueTracker(self.phi)
|
||||
self.theta_tracker = ValueTracker(self.theta)
|
||||
self.distance_tracker = ValueTracker(self.distance)
|
||||
self.gamma_tracker = ValueTracker(self.gamma)
|
||||
self.light_source = Point(self.light_source_start_point)
|
||||
self.frame_center = Point(self.frame_center)
|
||||
self.fixed_orientation_mobjects = dict()
|
||||
self.fixed_in_frame_mobjects = set()
|
||||
self.reset_rotation_matrix()
|
||||
|
||||
def capture(self, *mobjects, **kwargs):
|
||||
self.reset_rotation_matrix()
|
||||
Camera.capture(self, *mobjects, **kwargs)
|
||||
|
||||
def get_value_trackers(self):
|
||||
return [
|
||||
self.phi_tracker,
|
||||
self.theta_tracker,
|
||||
self.distance_tracker,
|
||||
self.gamma_tracker,
|
||||
]
|
||||
|
||||
def modified_rgbas(self, vmobject, rgbas):
|
||||
if not self.should_apply_shading:
|
||||
return rgbas
|
||||
if vmobject.shade_in_3d and (vmobject.get_num_points() > 0):
|
||||
light_source_point = self.light_source.points[0]
|
||||
if len(rgbas) < 2:
|
||||
shaded_rgbas = rgbas.repeat(2, axis=0)
|
||||
else:
|
||||
shaded_rgbas = np.array(rgbas[:2])
|
||||
shaded_rgbas[0, :3] = get_shaded_rgb(
|
||||
shaded_rgbas[0, :3],
|
||||
get_3d_vmob_start_corner(vmobject),
|
||||
get_3d_vmob_start_corner_unit_normal(vmobject),
|
||||
light_source_point,
|
||||
)
|
||||
shaded_rgbas[1, :3] = get_shaded_rgb(
|
||||
shaded_rgbas[1, :3],
|
||||
get_3d_vmob_end_corner(vmobject),
|
||||
get_3d_vmob_end_corner_unit_normal(vmobject),
|
||||
light_source_point,
|
||||
)
|
||||
return shaded_rgbas
|
||||
return rgbas
|
||||
|
||||
def get_stroke_rgbas(self, vmobject, background=False):
|
||||
return self.modified_rgbas(
|
||||
vmobject, vmobject.get_stroke_rgbas(background)
|
||||
)
|
||||
|
||||
def get_fill_rgbas(self, vmobject):
|
||||
return self.modified_rgbas(
|
||||
vmobject, vmobject.get_fill_rgbas()
|
||||
)
|
||||
|
||||
def get_mobjects_to_display(self, *args, **kwargs):
|
||||
mobjects = Camera.get_mobjects_to_display(
|
||||
self, *args, **kwargs
|
||||
)
|
||||
rot_matrix = self.get_rotation_matrix()
|
||||
|
||||
def z_key(mob):
|
||||
if not (hasattr(mob, "shade_in_3d") and mob.shade_in_3d):
|
||||
return np.inf
|
||||
# Assign a number to a three dimensional mobjects
|
||||
# based on how close it is to the camera
|
||||
return np.dot(
|
||||
mob.get_z_index_reference_point(),
|
||||
rot_matrix.T
|
||||
)[2]
|
||||
return sorted(mobjects, key=z_key)
|
||||
|
||||
def get_phi(self):
|
||||
return self.phi_tracker.get_value()
|
||||
|
||||
def get_theta(self):
|
||||
return self.theta_tracker.get_value()
|
||||
|
||||
def get_distance(self):
|
||||
return self.distance_tracker.get_value()
|
||||
|
||||
def get_gamma(self):
|
||||
return self.gamma_tracker.get_value()
|
||||
|
||||
def get_frame_center(self):
|
||||
return self.frame_center.points[0]
|
||||
|
||||
def set_phi(self, value):
|
||||
self.phi_tracker.set_value(value)
|
||||
|
||||
def set_theta(self, value):
|
||||
self.theta_tracker.set_value(value)
|
||||
|
||||
def set_distance(self, value):
|
||||
self.distance_tracker.set_value(value)
|
||||
|
||||
def set_gamma(self, value):
|
||||
self.gamma_tracker.set_value(value)
|
||||
|
||||
def set_frame_center(self, point):
|
||||
self.frame_center.move_to(point)
|
||||
|
||||
def reset_rotation_matrix(self):
|
||||
self.rotation_matrix = self.generate_rotation_matrix()
|
||||
|
||||
def get_rotation_matrix(self):
|
||||
return self.rotation_matrix
|
||||
|
||||
def generate_rotation_matrix(self):
|
||||
phi = self.get_phi()
|
||||
theta = self.get_theta()
|
||||
gamma = self.get_gamma()
|
||||
matrices = [
|
||||
rotation_about_z(-theta - 90 * DEGREES),
|
||||
rotation_matrix(-phi, RIGHT),
|
||||
rotation_about_z(gamma),
|
||||
]
|
||||
result = np.identity(3)
|
||||
for matrix in matrices:
|
||||
result = np.dot(matrix, result)
|
||||
return result
|
||||
|
||||
def project_points(self, points):
|
||||
frame_center = self.get_frame_center()
|
||||
distance = self.get_distance()
|
||||
rot_matrix = self.get_rotation_matrix()
|
||||
|
||||
points = points - frame_center
|
||||
points = np.dot(points, rot_matrix.T)
|
||||
zs = points[:, 2]
|
||||
for i in 0, 1:
|
||||
if self.exponential_projection:
|
||||
# Proper projedtion would involve multiplying
|
||||
# x and y by d / (d-z). But for points with high
|
||||
# z value that causes weird artifacts, and applying
|
||||
# the exponential helps smooth it out.
|
||||
factor = np.exp(zs / distance)
|
||||
lt0 = zs < 0
|
||||
factor[lt0] = (distance / (distance - zs[lt0]))
|
||||
else:
|
||||
factor = (distance / (distance - zs))
|
||||
factor[(distance - zs) < 0] = 10**6
|
||||
# clip_in_place(factor, 0, 10**6)
|
||||
points[:, i] *= factor
|
||||
points = points + frame_center
|
||||
return points
|
||||
|
||||
def project_point(self, point):
|
||||
return self.project_points(point.reshape((1, 3)))[0, :]
|
||||
|
||||
def transform_points_pre_display(self, mobject, points):
|
||||
points = super().transform_points_pre_display(mobject, points)
|
||||
fixed_orientation = mobject in self.fixed_orientation_mobjects
|
||||
fixed_in_frame = mobject in self.fixed_in_frame_mobjects
|
||||
|
||||
if fixed_in_frame:
|
||||
return points
|
||||
if fixed_orientation:
|
||||
center_func = self.fixed_orientation_mobjects[mobject]
|
||||
center = center_func()
|
||||
new_center = self.project_point(center)
|
||||
return points + (new_center - center)
|
||||
else:
|
||||
return self.project_points(points)
|
||||
|
||||
def add_fixed_orientation_mobjects(
|
||||
self, *mobjects,
|
||||
use_static_center_func=False,
|
||||
center_func=None):
|
||||
# This prevents the computation of mobject.get_center
|
||||
# every single time a projetion happens
|
||||
def get_static_center_func(mobject):
|
||||
point = mobject.get_center()
|
||||
return (lambda: point)
|
||||
|
||||
for mobject in mobjects:
|
||||
if center_func:
|
||||
func = center_func
|
||||
elif use_static_center_func:
|
||||
func = get_static_center_func(mobject)
|
||||
else:
|
||||
func = mobject.get_center
|
||||
for submob in mobject.get_family():
|
||||
self.fixed_orientation_mobjects[submob] = func
|
||||
|
||||
def add_fixed_in_frame_mobjects(self, *mobjects):
|
||||
for mobject in self.extract_mobject_family_members(mobjects):
|
||||
self.fixed_in_frame_mobjects.add(mobject)
|
||||
|
||||
def remove_fixed_orientation_mobjects(self, *mobjects):
|
||||
for mobject in self.extract_mobject_family_members(mobjects):
|
||||
if mobject in self.fixed_orientation_mobjects:
|
||||
self.fixed_orientation_mobjects.remove(mobject)
|
||||
|
||||
def remove_fixed_in_frame_mobjects(self, *mobjects):
|
||||
for mobject in self.extract_mobject_family_members(mobjects):
|
||||
if mobject in self.fixed_in_frame_mobjects:
|
||||
self.fixed_in_frame_mobjects.remove(mobject)
|
|
@ -154,7 +154,6 @@ DEFAULT_PIXEL_WIDTH = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_width"]
|
|||
DEFAULT_FRAME_RATE = 60
|
||||
|
||||
DEFAULT_STROKE_WIDTH = 4
|
||||
ANTI_ALIAS_WIDTH_OVER_FRAME_HEIGHT = 1e-3
|
||||
|
||||
FRAME_HEIGHT = 8.0
|
||||
FRAME_WIDTH = FRAME_HEIGHT * DEFAULT_PIXEL_WIDTH / DEFAULT_PIXEL_HEIGHT
|
||||
|
|
|
@ -155,6 +155,7 @@ class PatreonEndScreen(PatreonThanks, PiCreatureScene):
|
|||
"capitalize": True,
|
||||
"name_y_spacing": 0.6,
|
||||
"thanks_words": "Many thanks to this channel's supporters",
|
||||
"scroll_time": 20,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
@ -250,7 +251,7 @@ class PatreonEndScreen(PatreonThanks, PiCreatureScene):
|
|||
columns.target.to_edge(DOWN, buff=4)
|
||||
vect = columns.target.get_center() - columns.get_center()
|
||||
distance = get_norm(vect)
|
||||
wait_time = 20
|
||||
wait_time = self.scroll_time
|
||||
always_shift(
|
||||
columns,
|
||||
direction=normalize(vect),
|
||||
|
@ -267,7 +268,8 @@ class PatreonEndScreen(PatreonThanks, PiCreatureScene):
|
|||
"akostrikov": "Aleksandr Kostrikov",
|
||||
"Jacob Baxter": "Will Fleshman",
|
||||
"Sansword Huang": "SansWord@TW",
|
||||
"Still working on an upcoming skeptical humanist SciFi novels- Elux Luc": "Uber Miguel",
|
||||
"Sunil Nagaraj": "Ubiquity Ventures",
|
||||
"Nitu Kitchloo": "Ish Kitchloo",
|
||||
}
|
||||
for n1, n2 in modification_map.items():
|
||||
if name.lower() == n1.lower():
|
||||
|
@ -345,6 +347,7 @@ class Banner(Scene):
|
|||
pis.set_height(self.pi_height)
|
||||
pis.arrange(RIGHT, aligned_edge=DOWN)
|
||||
pis.move_to(self.pi_bottom, DOWN)
|
||||
self.pis = pis
|
||||
self.add(pis)
|
||||
|
||||
if self.use_date:
|
||||
|
|
|
@ -30,9 +30,6 @@ from manimlib.animation.transform import *
|
|||
from manimlib.animation.update import *
|
||||
|
||||
from manimlib.camera.camera import *
|
||||
from manimlib.camera.mapping_camera import *
|
||||
from manimlib.camera.moving_camera import *
|
||||
from manimlib.camera.three_d_camera import *
|
||||
|
||||
from manimlib.mobject.coordinate_systems import *
|
||||
from manimlib.mobject.changing import *
|
||||
|
@ -50,10 +47,10 @@ from manimlib.mobject.svg.drawings import *
|
|||
from manimlib.mobject.svg.svg_mobject import *
|
||||
from manimlib.mobject.svg.tex_mobject import *
|
||||
from manimlib.mobject.svg.text_mobject import *
|
||||
from manimlib.mobject.three_d_utils import *
|
||||
from manimlib.mobject.three_dimensions import *
|
||||
from manimlib.mobject.types.image_mobject import *
|
||||
from manimlib.mobject.types.point_cloud_mobject import *
|
||||
from manimlib.mobject.types.surface_mobject import *
|
||||
from manimlib.mobject.types.vectorized_mobject import *
|
||||
from manimlib.mobject.mobject_update_utils import *
|
||||
from manimlib.mobject.value_tracker import *
|
||||
|
|
|
@ -84,9 +84,12 @@ class CoordinateSystem():
|
|||
)
|
||||
return self.axis_labels
|
||||
|
||||
def get_graph(self, function, **kwargs):
|
||||
x_min = kwargs.pop("x_min", self.x_min)
|
||||
x_max = kwargs.pop("x_max", self.x_max)
|
||||
def get_graph(self, function, x_min=None, x_max=None, **kwargs):
|
||||
if x_min is None:
|
||||
x_min = self.x_min
|
||||
if x_max is None:
|
||||
x_max = self.x_max
|
||||
|
||||
graph = ParametricFunction(
|
||||
lambda t: self.coords_to_point(t, function(t)),
|
||||
t_min=x_min,
|
||||
|
|
|
@ -3,9 +3,6 @@ from manimlib.mobject.geometry import Rectangle
|
|||
from manimlib.utils.config_ops import digest_config
|
||||
|
||||
|
||||
# TODO, put CameraFrame in here?
|
||||
|
||||
|
||||
class ScreenRectangle(Rectangle):
|
||||
CONFIG = {
|
||||
"aspect_ratio": 16.0 / 9.0,
|
||||
|
|
|
@ -974,7 +974,7 @@ class Mobject(Container):
|
|||
def arrange_in_grid(self, n_rows=None, n_cols=None, **kwargs):
|
||||
submobs = self.submobjects
|
||||
if n_rows is None and n_cols is None:
|
||||
n_cols = int(np.sqrt(len(submobs)))
|
||||
n_rows = int(np.sqrt(len(submobs)))
|
||||
|
||||
if n_rows is not None:
|
||||
v1 = RIGHT
|
||||
|
|
|
@ -9,6 +9,9 @@ from manimlib.mobject.svg.svg_mobject import SVGMobject
|
|||
from manimlib.utils.config_ops import digest_config
|
||||
|
||||
|
||||
TEXT_MOB_SCALE_FACTOR = 0.05
|
||||
|
||||
|
||||
class TextSetting(object):
|
||||
def __init__(self, start, end, font, slant, weight, line_num=-1):
|
||||
self.start = start
|
||||
|
@ -45,8 +48,23 @@ class Text(SVGMobject):
|
|||
self.lsh = self.size if self.lsh == -1 else self.lsh
|
||||
|
||||
file_name = self.text2svg()
|
||||
self.remove_last_M(file_name)
|
||||
SVGMobject.__init__(self, file_name, **config)
|
||||
|
||||
nppc = self.n_points_per_curve
|
||||
for each in self:
|
||||
if len(each.points) == 0:
|
||||
continue
|
||||
points = each.points
|
||||
last = points[0]
|
||||
each.clear_points()
|
||||
for index, point in enumerate(points):
|
||||
each.append_points([point])
|
||||
if index != len(points) - 1 and (index + 1) % nppc == 0 and any(point != points[index+1]):
|
||||
each.add_line_to(last)
|
||||
last = points[index + 1]
|
||||
each.add_line_to(last)
|
||||
|
||||
if self.t2c:
|
||||
self.set_color_by_t2c()
|
||||
if self.gradient:
|
||||
|
@ -55,7 +73,15 @@ class Text(SVGMobject):
|
|||
self.set_color_by_t2g()
|
||||
|
||||
# anti-aliasing
|
||||
self.scale(0.1)
|
||||
if self.height is None:
|
||||
self.scale(TEXT_MOB_SCALE_FACTOR)
|
||||
|
||||
def remove_last_M(self, file_name):
|
||||
with open(file_name, 'r') as fpr:
|
||||
content = fpr.read()
|
||||
content = re.sub(r'Z M [^A-Za-z]*? "\/>', 'Z "/>', content)
|
||||
with open(file_name, 'w') as fpw:
|
||||
fpw.write(content)
|
||||
|
||||
def find_indexes(self, word):
|
||||
m = re.match(r'\[([0-9\-]{0,}):([0-9\-]{0,})\]', word)
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from manimlib.constants import ORIGIN
|
||||
from manimlib.utils.space_ops import get_unit_normal
|
||||
|
||||
|
||||
# TODO, these ideas should be deprecated
|
||||
|
||||
def get_3d_vmob_gradient_start_and_end_points(vmob):
|
||||
return (
|
||||
get_3d_vmob_start_corner(vmob),
|
||||
get_3d_vmob_end_corner(vmob),
|
||||
)
|
||||
|
||||
|
||||
def get_3d_vmob_start_corner_index(vmob):
|
||||
return 0
|
||||
|
||||
|
||||
def get_3d_vmob_end_corner_index(vmob):
|
||||
return ((len(vmob.points) - 1) // 6) * 3
|
||||
|
||||
|
||||
def get_3d_vmob_start_corner(vmob):
|
||||
if vmob.get_num_points() == 0:
|
||||
return np.array(ORIGIN)
|
||||
return vmob.points[get_3d_vmob_start_corner_index(vmob)]
|
||||
|
||||
|
||||
def get_3d_vmob_end_corner(vmob):
|
||||
if vmob.get_num_points() == 0:
|
||||
return np.array(ORIGIN)
|
||||
return vmob.points[get_3d_vmob_end_corner_index(vmob)]
|
||||
|
||||
|
||||
def get_3d_vmob_unit_normal(vmob, point_index):
|
||||
n_points = vmob.get_num_points()
|
||||
if vmob.get_num_points() == 0:
|
||||
return np.array(ORIGIN)
|
||||
i = point_index
|
||||
im1 = i - 1 if i > 0 else (n_points - 2)
|
||||
ip1 = i + 1 if i < (n_points - 1) else 1
|
||||
return get_unit_normal(
|
||||
vmob.points[ip1] - vmob.points[i],
|
||||
vmob.points[im1] - vmob.points[i],
|
||||
)
|
||||
|
||||
|
||||
def get_3d_vmob_start_corner_unit_normal(vmob):
|
||||
return get_3d_vmob_unit_normal(
|
||||
vmob, get_3d_vmob_start_corner_index(vmob)
|
||||
)
|
||||
|
||||
|
||||
def get_3d_vmob_end_corner_unit_normal(vmob):
|
||||
return get_3d_vmob_unit_normal(
|
||||
vmob, get_3d_vmob_end_corner_index(vmob)
|
||||
)
|
|
@ -1,64 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from manimlib.constants import ORIGIN
|
||||
from manimlib.constants import UP
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
from manimlib.utils.space_ops import get_unit_normal
|
||||
|
||||
|
||||
# TODO, these ideas should be deprecated
|
||||
|
||||
|
||||
def get_3d_vmob_gradient_start_and_end_points(vmob):
|
||||
return (
|
||||
get_3d_vmob_start_corner(vmob),
|
||||
get_3d_vmob_end_corner(vmob),
|
||||
)
|
||||
|
||||
|
||||
def get_3d_vmob_start_corner_index(vmob):
|
||||
return 0
|
||||
|
||||
|
||||
def get_3d_vmob_end_corner_index(vmob):
|
||||
return ((len(vmob.points) - 1) // 6) * 3
|
||||
|
||||
|
||||
def get_3d_vmob_start_corner(vmob):
|
||||
if vmob.get_num_points() == 0:
|
||||
return np.array(ORIGIN)
|
||||
return vmob.points[get_3d_vmob_start_corner_index(vmob)]
|
||||
|
||||
|
||||
def get_3d_vmob_end_corner(vmob):
|
||||
if vmob.get_num_points() == 0:
|
||||
return np.array(ORIGIN)
|
||||
return vmob.points[get_3d_vmob_end_corner_index(vmob)]
|
||||
|
||||
|
||||
def get_3d_vmob_unit_normal(vmob, point_index):
|
||||
n_points = vmob.get_num_points()
|
||||
if len(vmob.get_anchors()) <= 2:
|
||||
return np.array(UP)
|
||||
i = point_index
|
||||
im3 = i - 3 if i > 2 else (n_points - 4)
|
||||
ip3 = i + 3 if i < (n_points - 3) else 3
|
||||
unit_normal = get_unit_normal(
|
||||
vmob.points[ip3] - vmob.points[i],
|
||||
vmob.points[im3] - vmob.points[i],
|
||||
)
|
||||
if get_norm(unit_normal) == 0:
|
||||
return np.array(UP)
|
||||
return unit_normal
|
||||
|
||||
|
||||
def get_3d_vmob_start_corner_unit_normal(vmob):
|
||||
return get_3d_vmob_unit_normal(
|
||||
vmob, get_3d_vmob_start_corner_index(vmob)
|
||||
)
|
||||
|
||||
|
||||
def get_3d_vmob_end_corner_unit_normal(vmob):
|
||||
return get_3d_vmob_unit_normal(
|
||||
vmob, get_3d_vmob_end_corner_index(vmob)
|
||||
)
|
|
@ -1,29 +1,14 @@
|
|||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import Square
|
||||
from manimlib.mobject.types.surface_mobject import SurfaceMobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.utils.iterables import listify
|
||||
from manimlib.utils.space_ops import z_to_vector
|
||||
|
||||
##############
|
||||
|
||||
|
||||
# TODO, replace these with a special 3d type, not VMobject
|
||||
|
||||
|
||||
class ThreeDVMobject(VMobject):
|
||||
class ParametricSurface(SurfaceMobject):
|
||||
CONFIG = {
|
||||
"shade_in_3d": True,
|
||||
}
|
||||
|
||||
|
||||
class ParametricSurface(VGroup):
|
||||
CONFIG = {
|
||||
"u_min": 0,
|
||||
"u_max": 1,
|
||||
"v_min": 0,
|
||||
"v_max": 1,
|
||||
"resolution": 32,
|
||||
"u_range": (0, 1),
|
||||
"v_range": (0, 1),
|
||||
"resolution": (32, 32),
|
||||
"surface_piece_config": {},
|
||||
"fill_color": BLUE_D,
|
||||
"fill_opacity": 1.0,
|
||||
|
@ -34,93 +19,72 @@ class ParametricSurface(VGroup):
|
|||
"pre_function_handle_to_anchor_scale_factor": 0.00001,
|
||||
}
|
||||
|
||||
def __init__(self, func, **kwargs):
|
||||
VGroup.__init__(self, **kwargs)
|
||||
self.func = func
|
||||
self.setup_in_uv_space()
|
||||
self.apply_function(lambda p: func(p[0], p[1]))
|
||||
if self.should_make_jagged:
|
||||
self.make_jagged()
|
||||
|
||||
def get_u_values_and_v_values(self):
|
||||
res = listify(self.resolution)
|
||||
if len(res) == 1:
|
||||
u_res = v_res = res[0]
|
||||
def __init__(self, function=None, **kwargs):
|
||||
if function is None:
|
||||
self.uv_func = self.func
|
||||
else:
|
||||
u_res, v_res = res
|
||||
u_min = self.u_min
|
||||
u_max = self.u_max
|
||||
v_min = self.v_min
|
||||
v_max = self.v_max
|
||||
self.uv_func = function
|
||||
super().__init__(**kwargs)
|
||||
|
||||
u_values = np.linspace(u_min, u_max, u_res + 1)
|
||||
v_values = np.linspace(v_min, v_max, v_res + 1)
|
||||
|
||||
return u_values, v_values
|
||||
|
||||
def setup_in_uv_space(self):
|
||||
u_values, v_values = self.get_u_values_and_v_values()
|
||||
faces = VGroup()
|
||||
for i in range(len(u_values) - 1):
|
||||
for j in range(len(v_values) - 1):
|
||||
u1, u2 = u_values[i:i + 2]
|
||||
v1, v2 = v_values[j:j + 2]
|
||||
face = ThreeDVMobject()
|
||||
face.set_points_as_corners([
|
||||
[u1, v1, 0],
|
||||
[u2, v1, 0],
|
||||
[u2, v2, 0],
|
||||
[u1, v2, 0],
|
||||
[u1, v1, 0],
|
||||
])
|
||||
faces.add(face)
|
||||
face.u_index = i
|
||||
face.v_index = j
|
||||
face.u1 = u1
|
||||
face.u2 = u2
|
||||
face.v1 = v1
|
||||
face.v2 = v2
|
||||
faces.set_fill(
|
||||
color=self.fill_color,
|
||||
opacity=self.fill_opacity
|
||||
def init_points(self):
|
||||
epsilon = 1e-6 # For differentials
|
||||
nu, nv = self.resolution
|
||||
u_range = np.linspace(*self.u_range, nu + 1)
|
||||
v_range = np.linspace(*self.v_range, nv + 1)
|
||||
# List of three grids, [Pure uv values, those nudged by du, those nudged by dv]
|
||||
uv_grids = [
|
||||
np.array([[[u, v] for v in v_range] for u in u_range])
|
||||
for (du, dv) in [(0, 0), (epsilon, 0), (0, epsilon)]
|
||||
]
|
||||
point_grid, points_nudged_du, points_nudged_dv = [
|
||||
np.apply_along_axis(lambda p: self.uv_func(*p), 2, uv_grid)
|
||||
for uv_grid in uv_grids
|
||||
]
|
||||
normal_grid = np.cross(
|
||||
(points_nudged_du - point_grid) / epsilon,
|
||||
(points_nudged_dv - point_grid) / epsilon,
|
||||
)
|
||||
faces.set_stroke(
|
||||
color=self.stroke_color,
|
||||
width=self.stroke_width,
|
||||
opacity=self.stroke_opacity,
|
||||
|
||||
self.set_points(
|
||||
self.get_triangle_ready_array_from_grid(point_grid),
|
||||
self.get_triangle_ready_array_from_grid(normal_grid),
|
||||
)
|
||||
self.add(*faces)
|
||||
if self.checkerboard_colors:
|
||||
self.set_fill_by_checkerboard(*self.checkerboard_colors)
|
||||
|
||||
def set_fill_by_checkerboard(self, *colors, opacity=None):
|
||||
n_colors = len(colors)
|
||||
for face in self:
|
||||
c_index = (face.u_index + face.v_index) % n_colors
|
||||
face.set_fill(colors[c_index], opacity=opacity)
|
||||
# self.points = point_grid[indices]
|
||||
|
||||
def get_triangle_ready_array_from_grid(self, grid):
|
||||
# Given a grid, say of points or normals, this returns an Nx3 array
|
||||
# whose rows are elements from this grid in such such a way that successive
|
||||
# triplets of points form triangles covering the grid.
|
||||
nu = grid.shape[0] - 1
|
||||
nv = grid.shape[1] - 1
|
||||
dim = grid.shape[2]
|
||||
arr = np.zeros((nu * nv * 6, dim))
|
||||
# To match the triangles covering this surface
|
||||
arr[0::6] = grid[:-1, :-1].reshape((nu * nv, 3)) # Top left
|
||||
arr[1::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left
|
||||
arr[2::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right
|
||||
arr[3::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right
|
||||
arr[4::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left
|
||||
arr[5::6] = grid[+1:, +1:].reshape((nu * nv, 3)) # Bottom right
|
||||
return arr
|
||||
|
||||
def func(self, u, v):
|
||||
pass
|
||||
|
||||
|
||||
# Specific shapes
|
||||
|
||||
# Sphere, cylinder, cube, prism
|
||||
|
||||
class Sphere(ParametricSurface):
|
||||
CONFIG = {
|
||||
"resolution": (12, 24),
|
||||
"radius": 1,
|
||||
"u_min": 0.001,
|
||||
"u_max": PI - 0.001,
|
||||
"v_min": 0,
|
||||
"v_max": TAU,
|
||||
"u_range": (0, PI),
|
||||
"v_range": (0, TAU),
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
ParametricSurface.__init__(
|
||||
self, self.func, **kwargs
|
||||
)
|
||||
self.scale(self.radius)
|
||||
|
||||
def func(self, u, v):
|
||||
return np.array([
|
||||
return self.radius * np.array([
|
||||
np.cos(v) * np.sin(u),
|
||||
np.sin(v) * np.sin(u),
|
||||
np.cos(u)
|
||||
|
|
76
manimlib/mobject/types/surface_mobject.py
Normal file
76
manimlib/mobject/types/surface_mobject.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
import numpy as np
|
||||
import moderngl
|
||||
|
||||
# from PIL import Image
|
||||
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.utils.color import color_to_rgba
|
||||
|
||||
|
||||
class SurfaceMobject(Mobject):
|
||||
CONFIG = {
|
||||
"color": GREY,
|
||||
"opacity": 1,
|
||||
"gloss": 1.0,
|
||||
"render_primative": moderngl.TRIANGLES,
|
||||
# "render_primative": moderngl.TRIANGLE_STRIP,
|
||||
"vert_shader_file": "surface_vert.glsl",
|
||||
"frag_shader_file": "surface_frag.glsl",
|
||||
"shader_dtype": [
|
||||
('point', np.float32, (3,)),
|
||||
('normal', np.float32, (3,)),
|
||||
('color', np.float32, (4,)),
|
||||
('gloss', np.float32, (1,)),
|
||||
# ('im_coords', np.float32, (2,)),
|
||||
]
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def init_points(self):
|
||||
self.points = np.zeros((0, self.dim))
|
||||
self.normals = np.zeros((0, self.dim))
|
||||
|
||||
def init_colors(self):
|
||||
self.set_color(self.color, self.opacity)
|
||||
|
||||
def set_points(self, points, normals=None):
|
||||
self.points = np.array(points)
|
||||
if normals is None:
|
||||
v01 = points[1:-1] - points[:-2]
|
||||
v02 = points[2:] - points[:-2]
|
||||
crosses = np.cross(v01, v02)
|
||||
crosses[1::2] *= -1 # Because of reversed orientation of every other triangle in the strip
|
||||
self.normals = np.vstack([
|
||||
crosses,
|
||||
crosses[-1:].repeat(2, 0) # Repeat last entry twice
|
||||
])
|
||||
else:
|
||||
self.normals = np.array(normals)
|
||||
|
||||
def set_color(self, color, opacity):
|
||||
# TODO, allow for multiple colors
|
||||
rgba = color_to_rgba(color, opacity)
|
||||
self.rgbas = np.array([rgba])
|
||||
|
||||
def apply_function(self, function, **kwargs):
|
||||
# Apply it to infinitesimal neighbors to preserve normals
|
||||
pass
|
||||
|
||||
def rotate(self, axis, angle, **kwargs):
|
||||
# Account for normals
|
||||
pass
|
||||
|
||||
def stretch(self, factor, dim, **kwargs):
|
||||
# Account for normals
|
||||
pass
|
||||
|
||||
def get_shader_data(self):
|
||||
data = self.get_blank_shader_data_array(len(self.points))
|
||||
data["point"] = self.points
|
||||
data["normal"] = self.normals
|
||||
data["color"] = self.rgbas
|
||||
data["gloss"] = self.gloss
|
||||
return data
|
|
@ -1,12 +1,13 @@
|
|||
import itertools as it
|
||||
import operator as op
|
||||
import moderngl
|
||||
|
||||
from colour import Color
|
||||
from functools import reduce
|
||||
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.mobject import Point
|
||||
from manimlib.mobject.three_d_utils import get_3d_vmob_gradient_start_and_end_points
|
||||
from manimlib.utils.bezier import bezier
|
||||
from manimlib.utils.bezier import get_smooth_handle_points
|
||||
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
|
||||
|
@ -19,10 +20,12 @@ from manimlib.utils.iterables import make_even
|
|||
from manimlib.utils.iterables import stretch_array_to_length
|
||||
from manimlib.utils.iterables import stretch_array_to_length_with_interpolation
|
||||
from manimlib.utils.iterables import listify
|
||||
from manimlib.utils.space_ops import cross2d
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
from manimlib.utils.space_ops import angle_between_vectors
|
||||
from manimlib.utils.space_ops import cross2d
|
||||
from manimlib.utils.space_ops import earclip_triangulation
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
from manimlib.utils.space_ops import get_unit_normal
|
||||
from manimlib.utils.space_ops import z_to_vector
|
||||
from manimlib.utils.shaders import get_shader_info
|
||||
|
||||
|
||||
|
@ -58,21 +61,26 @@ class VMobject(Mobject):
|
|||
"fill_frag_shader_file": "quadratic_bezier_fill_frag.glsl",
|
||||
# Could also be Bevel, Miter, Round
|
||||
"joint_type": "auto",
|
||||
# Positive gloss up to 1 makes it reflect the light.
|
||||
"gloss": 0.2,
|
||||
"render_primative": moderngl.TRIANGLES,
|
||||
"triangulation_locked": False,
|
||||
"fill_dtype": [
|
||||
('point', np.float32, (3,)),
|
||||
('unit_normal', np.float32, (3,)),
|
||||
('color', np.float32, (4,)),
|
||||
('fill_all', np.float32, (1,)),
|
||||
('orientation', np.float32, (1,)),
|
||||
('gloss', np.float32, (1,)),
|
||||
],
|
||||
"stroke_dtype": [
|
||||
("point", np.float32, (3,)),
|
||||
("prev_point", np.float32, (3,)),
|
||||
("next_point", np.float32, (3,)),
|
||||
('unit_normal', np.float32, (3,)),
|
||||
("stroke_width", np.float32, (1,)),
|
||||
("color", np.float32, (4,)),
|
||||
("joint_type", np.float32, (1,)),
|
||||
("gloss", np.float32, (1,)),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -228,6 +236,14 @@ class VMobject(Mobject):
|
|||
super().fade(darkness, family)
|
||||
return self
|
||||
|
||||
def set_gloss(self, gloss, family=True):
|
||||
if family:
|
||||
for sm in self.get_family():
|
||||
sm.gloss = gloss
|
||||
else:
|
||||
self.gloss = gloss
|
||||
return self
|
||||
|
||||
def get_fill_rgbas(self):
|
||||
try:
|
||||
return self.fill_rgbas
|
||||
|
@ -654,6 +670,14 @@ class VMobject(Mobject):
|
|||
self.get_end_anchors(),
|
||||
))))
|
||||
|
||||
def get_points_without_null_curves(self, atol=1e-9):
|
||||
nppc = self.n_points_per_curve
|
||||
distinct_curves = reduce(op.or_, [
|
||||
(abs(self.points[i::nppc] - self.points[0::nppc]) > atol).any(1)
|
||||
for i in range(1, nppc)
|
||||
])
|
||||
return self.points[distinct_curves.repeat(nppc)]
|
||||
|
||||
def get_arc_length(self, n_sample_points=None):
|
||||
if n_sample_points is None:
|
||||
n_sample_points = 4 * self.get_num_curves() + 1
|
||||
|
@ -665,6 +689,38 @@ class VMobject(Mobject):
|
|||
norms = np.array([get_norm(d) for d in diffs])
|
||||
return norms.sum()
|
||||
|
||||
def get_area_vector(self):
|
||||
# Returns a vector whose length is the area bound by
|
||||
# the polygon formed by the anchor points, pointing
|
||||
# in a direction perpendicular to the polygon according
|
||||
# to the right hand rule.
|
||||
if self.has_no_points():
|
||||
return np.zeros(3)
|
||||
|
||||
nppc = self.n_points_per_curve
|
||||
p0 = self.points[0::nppc]
|
||||
p1 = self.points[nppc - 1::nppc]
|
||||
|
||||
# Each term goes through all edges [(x1, y1, z1), (x2, y2, z2)]
|
||||
return 0.5 * np.array([
|
||||
sum((p0[:, 1] + p1[:, 1]) * (p1[:, 2] - p0[:, 2])), # Add up (y1 + y2)*(z2 - z1)
|
||||
sum((p0[:, 2] + p1[:, 2]) * (p1[:, 0] - p0[:, 0])), # Add up (z1 + z2)*(x2 - x1)
|
||||
sum((p0[:, 0] + p1[:, 0]) * (p1[:, 1] - p0[:, 1])), # Add up (x1 + x2)*(y2 - y1)
|
||||
])
|
||||
|
||||
def get_unit_normal_vector(self):
|
||||
if len(self.points) < 3:
|
||||
return OUT
|
||||
area_vect = self.get_area_vector()
|
||||
area = get_norm(area_vect)
|
||||
if area > 0:
|
||||
return area_vect / area
|
||||
else:
|
||||
return get_unit_normal(
|
||||
self.points[1] - self.points[0],
|
||||
self.points[2] - self.points[1],
|
||||
)
|
||||
|
||||
# Alignment
|
||||
def align_points(self, vmobject):
|
||||
self.align_rgbas(vmobject)
|
||||
|
@ -896,15 +952,20 @@ class VMobject(Mobject):
|
|||
if len(stroke_width) > 1:
|
||||
stroke_width = self.stretched_style_array_matching_points(stroke_width)
|
||||
|
||||
data = self.get_blank_shader_data_array(len(self.points), "stroke_data")
|
||||
data['point'] = self.points
|
||||
data['prev_point'][:3] = self.points[-3:]
|
||||
data['prev_point'][3:] = self.points[:-3]
|
||||
data['next_point'][:-3] = self.points[3:]
|
||||
data['next_point'][-3:] = self.points[:3]
|
||||
data['stroke_width'][:, 0] = stroke_width
|
||||
data['color'] = rgbas
|
||||
data['joint_type'] = joint_type_to_code[self.joint_type]
|
||||
points = self.get_points_without_null_curves()
|
||||
nppc = self.n_points_per_curve
|
||||
|
||||
data = self.get_blank_shader_data_array(len(points), "stroke_data")
|
||||
data["point"] = points
|
||||
data["prev_point"][:nppc] = points[-nppc:]
|
||||
data["prev_point"][nppc:] = points[:-nppc]
|
||||
data["next_point"][:-nppc] = points[nppc:]
|
||||
data["next_point"][-nppc:] = points[:nppc]
|
||||
data["unit_normal"] = self.get_unit_normal_vector()
|
||||
data["stroke_width"][:, 0] = stroke_width
|
||||
data["color"] = rgbas
|
||||
data["joint_type"] = joint_type_to_code[self.joint_type]
|
||||
data["gloss"] = self.gloss
|
||||
return data
|
||||
|
||||
def lock_triangulation(self, family=True):
|
||||
|
@ -912,7 +973,6 @@ class VMobject(Mobject):
|
|||
for mob in mobs:
|
||||
mob.triangulation_locked = False
|
||||
mob.saved_triangulation = mob.get_triangulation()
|
||||
mob.saved_orientation = mob.get_orientation()
|
||||
mob.triangulation_locked = True
|
||||
return self
|
||||
|
||||
|
@ -925,26 +985,12 @@ class VMobject(Mobject):
|
|||
if sm.triangulation_locked:
|
||||
sm.lock_triangulation(family=False)
|
||||
|
||||
def get_signed_polygonal_area(self):
|
||||
nppc = self.n_points_per_curve
|
||||
p0 = self.points[0::nppc]
|
||||
p1 = self.points[nppc - 1::nppc]
|
||||
# Add up (x1 + x2)*(y2 - y1) for all edges (x1, y1), (x2, y2)
|
||||
return sum((p0[:, 0] + p1[:, 0]) * (p1[:, 1] - p0[:, 1]))
|
||||
|
||||
def get_orientation(self):
|
||||
if self.triangulation_locked:
|
||||
return self.saved_orientation
|
||||
if self.has_no_points():
|
||||
return 0
|
||||
return np.sign(self.get_signed_polygonal_area())
|
||||
|
||||
def get_triangulation(self, orientation=None):
|
||||
def get_triangulation(self, normal_vector=None):
|
||||
# Figure out how to triangulate the interior to know
|
||||
# how to send the points as to the vertex shader.
|
||||
# First triangles come directly from the points
|
||||
if orientation is None:
|
||||
orientation = self.get_orientation()
|
||||
if normal_vector is None:
|
||||
normal_vector = self.get_unit_normal_vector()
|
||||
|
||||
if self.triangulation_locked:
|
||||
return self.saved_triangulation
|
||||
|
@ -952,7 +998,9 @@ class VMobject(Mobject):
|
|||
if len(self.points) <= 1:
|
||||
return []
|
||||
|
||||
points = self.points
|
||||
# Rotate points such that unit normal vector is OUT
|
||||
# TODO, 99% of the time this does nothing. Do a check for that?
|
||||
points = np.dot(self.points, z_to_vector(normal_vector))
|
||||
indices = np.arange(len(points), dtype=int)
|
||||
|
||||
b0s = points[0::3]
|
||||
|
@ -961,9 +1009,8 @@ class VMobject(Mobject):
|
|||
v01s = b1s - b0s
|
||||
v12s = b2s - b1s
|
||||
|
||||
# TODO, account for 3d
|
||||
crosses = cross2d(v01s, v12s)
|
||||
convexities = orientation * np.sign(crosses)
|
||||
convexities = np.sign(crosses)
|
||||
|
||||
atol = self.tolerance_for_point_equality
|
||||
end_of_loop = np.zeros(len(b0s), dtype=bool)
|
||||
|
@ -983,31 +1030,28 @@ class VMobject(Mobject):
|
|||
|
||||
# Triangulate
|
||||
inner_verts = points[inner_vert_indices]
|
||||
inner_tri_indices = inner_vert_indices[
|
||||
earclip_triangulation(inner_verts, rings)
|
||||
]
|
||||
inner_tri_indices = inner_vert_indices[earclip_triangulation(inner_verts, rings)]
|
||||
|
||||
tri_indices = np.hstack([indices, inner_tri_indices])
|
||||
return tri_indices
|
||||
|
||||
def get_fill_shader_data(self):
|
||||
points = self.points
|
||||
|
||||
orientation = self.get_orientation()
|
||||
tri_indices = self.get_triangulation(orientation)
|
||||
unit_normal = self.get_unit_normal_vector()
|
||||
tri_indices = self.get_triangulation(unit_normal)
|
||||
|
||||
# TODO, best way to enable multiple colors?
|
||||
rgbas = self.get_fill_rgbas()[:1]
|
||||
|
||||
data = self.get_blank_shader_data_array(len(tri_indices), "fill_data")
|
||||
data["point"] = points[tri_indices]
|
||||
data["unit_normal"] = unit_normal
|
||||
data["color"] = rgbas
|
||||
# Assume the triangulation is such that the first n_points points
|
||||
# are on the boundary, and the rest are in the interior
|
||||
data["fill_all"][:len(points)] = 0
|
||||
data["fill_all"][len(points):] = 1
|
||||
data["orientation"] = orientation
|
||||
|
||||
data["gloss"] = self.gloss
|
||||
return data
|
||||
|
||||
|
||||
|
|
|
@ -120,6 +120,8 @@ class Scene(Container):
|
|||
# Stack depth of 2 means the shell will use
|
||||
# the namespace of the caller, not this method
|
||||
shell(stack_depth=2)
|
||||
# End scene when exiting an embed.
|
||||
raise EndSceneEarlyException()
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
|
@ -527,6 +529,9 @@ class Scene(Container):
|
|||
|
||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
||||
self.mouse_drag_point.move_to(point)
|
||||
# Only if 3d rotation is enabled?
|
||||
self.camera.frame.increment_theta(-d_point[0])
|
||||
self.camera.frame.increment_phi(d_point[1])
|
||||
|
||||
def on_mouse_press(self, point, button, mods):
|
||||
pass
|
||||
|
@ -548,7 +553,7 @@ class Scene(Container):
|
|||
|
||||
def on_key_press(self, symbol, modifiers):
|
||||
if chr(symbol) == "r":
|
||||
self.camera.frame.restore()
|
||||
self.camera.frame.to_default_state()
|
||||
elif chr(symbol) == "z":
|
||||
self.zoom_on_scroll = True
|
||||
elif chr(symbol) == "q":
|
||||
|
|
|
@ -226,6 +226,7 @@ class SceneFileWriter(object):
|
|||
command += [
|
||||
'-vcodec', 'libx264',
|
||||
'-pix_fmt', 'yuv420p',
|
||||
# '-pix_fmt', 'yuv444p14le',
|
||||
]
|
||||
command += [temp_file_path]
|
||||
self.writing_process = sp.Popen(command, stdin=sp.PIPE)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from manimlib.animation.transform import ApplyMethod
|
||||
from manimlib.camera.three_d_camera import ThreeDCamera
|
||||
from manimlib.constants import DEGREES
|
||||
from manimlib.constants import PRODUCTION_QUALITY_CAMERA_CONFIG
|
||||
from manimlib.mobject.coordinate_systems import ThreeDAxes
|
||||
|
@ -12,9 +11,10 @@ from manimlib.utils.config_ops import digest_config
|
|||
from manimlib.utils.config_ops import merge_dicts_recursively
|
||||
|
||||
|
||||
# TODO, these seem deprecated.
|
||||
|
||||
class ThreeDScene(Scene):
|
||||
CONFIG = {
|
||||
"camera_class": ThreeDCamera,
|
||||
"ambient_camera_rotation": None,
|
||||
"default_angled_camera_orientation_kwargs": {
|
||||
"phi": 70 * DEGREES,
|
||||
|
|
23
manimlib/shaders/add_light.glsl
Normal file
23
manimlib/shaders/add_light.glsl
Normal file
|
@ -0,0 +1,23 @@
|
|||
vec4 add_light(vec4 raw_color, vec3 point, vec3 unit_normal, vec3 light_coords, float gloss){
|
||||
if(gloss == 0.0) return raw_color;
|
||||
|
||||
// TODO, do we actually want this? For VMobjects its nice to just choose whichever unit normal
|
||||
// is pointing towards the camera.
|
||||
if(unit_normal.z < 0){
|
||||
unit_normal *= -1;
|
||||
}
|
||||
|
||||
float camera_distance = 6; // TODO, read this in as a uniform?
|
||||
// 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;
|
||||
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 shine = 2 * gloss * exp(-1 * pow(1 - dot_prod, 2));
|
||||
float dp2 = dot(normalize(to_light), unit_normal);
|
||||
return vec4(
|
||||
mix(0.5, 1.0, max(dp2, 0)) * mix(raw_color.rgb, vec3(1.0), shine),
|
||||
raw_color.a
|
||||
);
|
||||
}
|
14
manimlib/shaders/get_gl_Position.glsl
Normal file
14
manimlib/shaders/get_gl_Position.glsl
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Assumes the following uniforms exist in the surrounding context:
|
||||
// uniform float aspect_ratio;
|
||||
// uniform float focal_distance;
|
||||
|
||||
vec4 get_gl_Position(vec3 point){
|
||||
point.x /= aspect_ratio;
|
||||
point.z /= focal_distance;
|
||||
point.xy /= max(1 - point.z, 0);
|
||||
// Todo, does this discontinuity add weirdness? Theoretically, by this point,
|
||||
// the z-coordiante of gl_Position only matter for z-indexing. The reason
|
||||
// for thie line is to avoid agressive clipping of distant points.
|
||||
if(point.z < 0) point.z *= 0.1;
|
||||
return vec4(point.xy, -point.z, 1);
|
||||
}
|
22
manimlib/shaders/get_unit_normal.glsl
Normal file
22
manimlib/shaders/get_unit_normal.glsl
Normal file
|
@ -0,0 +1,22 @@
|
|||
vec3 get_unit_normal(in vec3[3] points){
|
||||
float tol = 1e-6;
|
||||
vec3 v1 = normalize(points[1] - points[0]);
|
||||
vec3 v2 = normalize(points[2] - points[0]);
|
||||
vec3 cp = cross(v1, v2);
|
||||
float cp_norm = length(cp);
|
||||
if(cp_norm < tol){
|
||||
// Three points form a line, so find a normal vector
|
||||
// to that line in the plane shared with the z-axis
|
||||
vec3 k_hat = vec3(0.0, 0.0, 1.0);
|
||||
vec3 new_cp = cross(cross(v2, k_hat), v2);
|
||||
float new_cp_norm = length(new_cp);
|
||||
if(new_cp_norm < tol){
|
||||
// We only come here if all three points line up
|
||||
// on the z-axis.
|
||||
return vec3(0.0, 1.0, 0.0);
|
||||
// return k_hat;
|
||||
}
|
||||
return new_cp / new_cp_norm;
|
||||
}
|
||||
return cp / cp_norm;
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
#version 330
|
||||
|
||||
uniform float scale;
|
||||
uniform float aspect_ratio;
|
||||
uniform vec3 frame_center;
|
||||
uniform float anti_alias_width;
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float focal_distance;
|
||||
|
||||
uniform sampler2D Texture;
|
||||
|
||||
|
@ -14,16 +15,11 @@ out vec2 v_im_coords;
|
|||
out float v_opacity;
|
||||
|
||||
// Analog of import for manim only
|
||||
#INSERT rotate_point_for_frame.glsl
|
||||
#INSERT scale_and_shift_point_for_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT position_point_into_frame.glsl
|
||||
|
||||
void main(){
|
||||
v_im_coords = im_coords;
|
||||
v_opacity = opacity;
|
||||
gl_Position = vec4(
|
||||
rotate_point_for_frame(
|
||||
scale_and_shift_point_for_frame(point)
|
||||
),
|
||||
1.0
|
||||
);
|
||||
gl_Position = get_gl_Position(position_point_into_frame(point));
|
||||
}
|
9
manimlib/shaders/position_point_into_frame.glsl
Normal file
9
manimlib/shaders/position_point_into_frame.glsl
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Must be used in an environment with the following uniforms:
|
||||
// uniform mat4 to_screen_space;
|
||||
// uniform float focal_distance;
|
||||
|
||||
vec3 position_point_into_frame(vec3 point){
|
||||
// Apply the pre-computed to_screen_space matrix.
|
||||
vec4 new_point = to_screen_space * vec4(point, 1);
|
||||
return new_point.xyz;
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
// This file is not a shader, it's just a set of
|
||||
// functions meant to be inserted into other shaders.
|
||||
|
||||
// Must be inserted in a context with a definition for modify_distance_for_endpoints
|
||||
|
||||
// All of this is with respect to a curve that's been rotated/scaled
|
||||
// so that b0 = (0, 0) and b1 = (1, 0). That is, b2 entirely
|
||||
// determines the shape of the curve
|
||||
|
@ -16,36 +18,6 @@ vec2 bezier(float t, vec2 b2){
|
|||
);
|
||||
}
|
||||
|
||||
void compute_C_and_grad_C(float a, float b, vec2 p, out float Cxy, out vec2 grad_Cxy){
|
||||
// Curve has the implicit form x = a*y + b*sqrt(y), which is also
|
||||
// 0 = -x^2 + 2axy + b^2 y - a^2 y^2.
|
||||
Cxy = -p.x*p.x + 2 * a * p.x*p.y + b*b * p.y - a*a * p.y*p.y;
|
||||
|
||||
// Approximate distance to curve using the gradient of -x^2 + 2axy + b^2 y - a^2 y^2
|
||||
grad_Cxy = vec2(
|
||||
-2 * p.x + 2 * a * p.y, // del C / del x
|
||||
2 * a * p.x + b*b - 2 * a*a * p.y // del C / del y
|
||||
);
|
||||
}
|
||||
|
||||
// This function is flawed.
|
||||
float cheap_dist_to_curve(vec2 p, vec2 b2){
|
||||
float a = (b2.x - 2.0) / b2.y;
|
||||
float b = sign(b2.y) * 2.0 / sqrt(abs(b2.y));
|
||||
float x = p.x;
|
||||
float y = p.y;
|
||||
|
||||
// Curve has the implicit form x = a*y + b*sqrt(y), which is also
|
||||
// 0 = -x^2 + 2axy + b^2 y - a^2 y^2.
|
||||
float Cxy = -x * x + 2 * a * x * y + sign(b2.y) * b * b * y - a * a * y * y;
|
||||
|
||||
// Approximate distance to curve using the gradient of -x^2 + 2axy + b^2 y - a^2 y^2
|
||||
vec2 grad_Cxy = 2 * vec2(
|
||||
-x + a * y, // del C / del x
|
||||
a * x + b * b / 2 - a * a * y // del C / del y
|
||||
);
|
||||
return abs(Cxy / length(grad_Cxy));
|
||||
}
|
||||
|
||||
float cube_root(float x){
|
||||
return sign(x) * pow(abs(x), 1.0 / 3.0);
|
||||
|
@ -98,9 +70,9 @@ int cubic_solve(float a, float b, float c, float d, out float roots[3]){
|
|||
float dist_to_line(vec2 p, vec2 b2){
|
||||
float t = clamp(p.x / b2.x, 0, 1);
|
||||
float dist;
|
||||
if(t == 0) dist = length(p);
|
||||
if(t == 0) dist = length(p);
|
||||
else if(t == 1) dist = distance(p, b2);
|
||||
else dist = abs(p.y);
|
||||
else dist = abs(p.y);
|
||||
|
||||
return modify_distance_for_endpoints(p, dist, t);
|
||||
}
|
||||
|
@ -114,10 +86,9 @@ float dist_to_point_on_curve(vec2 p, float t, vec2 b2){
|
|||
}
|
||||
|
||||
|
||||
float min_dist_to_curve(vec2 p, vec2 b2, float degree, bool quick_approx){
|
||||
float min_dist_to_curve(vec2 p, vec2 b2, float degree){
|
||||
// Check if curve is really a a line
|
||||
if(degree == 1) return dist_to_line(p, b2);
|
||||
if(quick_approx) return cheap_dist_to_curve(p, b2);
|
||||
|
||||
// Try finding the exact sdf by solving the equation
|
||||
// (d/dt) dist^2(t) = 0, which amount to the following
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 light_source_position;
|
||||
uniform mat4 to_screen_space;
|
||||
|
||||
in vec4 color;
|
||||
in float fill_type;
|
||||
in float fill_all; // Either 0 or 1e
|
||||
in float uv_anti_alias_width;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
in vec3 global_unit_normal;
|
||||
in float orientation;
|
||||
in vec2 uv_coords;
|
||||
in vec2 wz_coords;
|
||||
in vec2 uv_b2;
|
||||
in float bezier_degree;
|
||||
in float gloss;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
const float FILL_INSIDE = 0;
|
||||
const float FILL_OUTSIDE = 1;
|
||||
const float FILL_ALL = 2;
|
||||
|
||||
|
||||
// Needed for quadratic_bezier_distance
|
||||
// Needed for quadratic_bezier_distance insertion below
|
||||
float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
||||
return dist;
|
||||
}
|
||||
|
@ -25,28 +26,48 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
|||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of quadratic_bezier_sdf.glsl
|
||||
#INSERT quadratic_bezier_distance.glsl
|
||||
|
||||
|
||||
bool is_inside_curve(){
|
||||
if(bezier_degree < 2) return false;
|
||||
|
||||
float value = wz_coords.x * wz_coords.x - wz_coords.y;
|
||||
if(fill_type == FILL_INSIDE) return value < 0;
|
||||
if(fill_type == FILL_OUTSIDE) return value > 0;
|
||||
return false;
|
||||
}
|
||||
#INSERT add_light.glsl
|
||||
|
||||
|
||||
float sdf(){
|
||||
if(is_inside_curve()) return -1;
|
||||
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree, false);
|
||||
if(bezier_degree < 2){
|
||||
return abs(uv_coords[1]);
|
||||
}
|
||||
float u2 = uv_b2.x;
|
||||
float v2 = uv_b2.y;
|
||||
// For really flat curves, just take the distance to x-axis
|
||||
if(abs(v2 / u2) < 0.1 * uv_anti_alias_width){
|
||||
return abs(uv_coords[1]);
|
||||
}
|
||||
// For flat-ish curves, take the curve
|
||||
else if(abs(v2 / u2) < 0.5 * uv_anti_alias_width){
|
||||
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
|
||||
}
|
||||
// I know, I don't love this amount of arbitrary-seeming branching either,
|
||||
// but a number of strange dimples and bugs pop up otherwise.
|
||||
|
||||
// This converts uv_coords to yet another space where the bezier points sit on
|
||||
// (0, 0), (1/2, 0) and (1, 1), so that the curve can be expressed implicityly
|
||||
// as y = x^2.
|
||||
mat2 to_simple_space = mat2(
|
||||
v2, 0,
|
||||
2 - u2, 4 * v2
|
||||
);
|
||||
vec2 p = to_simple_space * uv_coords;
|
||||
// Sign takes care of whether we should be filling the inside or outside of curve.
|
||||
float sn = orientation * sign(v2);
|
||||
float Fp = sn * (p.x * p.x - p.y);
|
||||
vec2 grad = vec2(
|
||||
-2 * p.x * v2, // del C / del u
|
||||
4 * v2 - 4 * p.x * (2 - u2) // del C / del v
|
||||
);
|
||||
return Fp / length(grad);
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
if (color.a == 0) discard;
|
||||
frag_color = color;
|
||||
if (fill_type == FILL_ALL) return;
|
||||
frag_color = add_light(color, xyz_coords, global_unit_normal, light_source_position, gloss);
|
||||
if (fill_all == 1.0) return;
|
||||
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
|
||||
// frag_color.a += 0.2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,176 +3,119 @@
|
|||
layout (triangles) in;
|
||||
layout (triangle_strip, max_vertices = 5) out;
|
||||
|
||||
uniform float scale;
|
||||
uniform float aspect_ratio;
|
||||
uniform float anti_alias_width;
|
||||
uniform vec3 frame_center;
|
||||
// Needed for get_gl_Position
|
||||
uniform float aspect_ratio;
|
||||
uniform float focal_distance;
|
||||
|
||||
in vec3 bp[3];
|
||||
in vec3 v_global_unit_normal[3];
|
||||
in vec4 v_color[3];
|
||||
in float v_fill_all[3];
|
||||
in float v_orientation[3];
|
||||
in float v_gloss[3];
|
||||
|
||||
out vec4 color;
|
||||
out float fill_type;
|
||||
out float gloss;
|
||||
out float fill_all;
|
||||
out float uv_anti_alias_width;
|
||||
|
||||
out vec3 xyz_coords;
|
||||
out vec3 global_unit_normal;
|
||||
out float orientation;
|
||||
// uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal
|
||||
out vec2 uv_coords;
|
||||
out vec2 uv_b2;
|
||||
// wz space is where b0 = (0, 0), b1 = (0.5, 0), b2 = (1, 1)
|
||||
out vec2 wz_coords;
|
||||
|
||||
out float bezier_degree;
|
||||
|
||||
const float FILL_INSIDE = 0;
|
||||
const float FILL_OUTSIDE = 1;
|
||||
const float FILL_ALL = 2;
|
||||
|
||||
const float SQRT5 = 2.236068;
|
||||
|
||||
|
||||
// To my knowledge, there is no notion of #include for shaders,
|
||||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of named file
|
||||
// in manim replaces this line with the contents of named file
|
||||
#INSERT quadratic_bezier_geometry_functions.glsl
|
||||
#INSERT scale_and_shift_point_for_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT get_unit_normal.glsl
|
||||
|
||||
|
||||
mat3 get_xy_to_wz(vec2 b0, vec2 b1, vec2 b2){
|
||||
// If linear or null, this matrix is not needed
|
||||
if(bezier_degree < 2) return mat3(1.0);
|
||||
|
||||
vec2 inv_col1 = 2 * (b1 - b0);
|
||||
vec2 inv_col2 = b2 - 2 * b1 + b0;
|
||||
float inv_det = cross(inv_col1, inv_col2);
|
||||
|
||||
mat3 transform = mat3(
|
||||
inv_col2.y, -inv_col1.y, 0,
|
||||
-inv_col2.x, inv_col1.x, 0,
|
||||
0, 0, inv_det
|
||||
) / inv_det;
|
||||
|
||||
mat3 shift = mat3(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
-b0.x, -b0.y, 1
|
||||
);
|
||||
return transform * shift;
|
||||
void emit_vertex_wrapper(vec3 point, int index){
|
||||
color = v_color[index];
|
||||
gloss = v_gloss[index];
|
||||
global_unit_normal = v_global_unit_normal[index];
|
||||
xyz_coords = point;
|
||||
gl_Position = get_gl_Position(xyz_coords);
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
|
||||
void emit_simple_triangle(){
|
||||
for(int i = 0; i < 3; i++){
|
||||
color = v_color[i];
|
||||
gl_Position = vec4(
|
||||
scale_and_shift_point_for_frame(bp[i]),
|
||||
1.0
|
||||
);
|
||||
EmitVertex();
|
||||
emit_vertex_wrapper(bp[i], i);
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
||||
void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2, float orientation){
|
||||
void emit_pentagon(vec3[3] points, vec3 normal){
|
||||
vec3 p0 = points[0];
|
||||
vec3 p1 = points[1];
|
||||
vec3 p2 = points[2];
|
||||
// Tangent vectors
|
||||
vec2 t01 = normalize(bp1 - bp0);
|
||||
vec2 t12 = normalize(bp2 - bp1);
|
||||
|
||||
// Inside and left turn -> rot right -> -1
|
||||
// Outside and left turn -> rot left -> +1
|
||||
// Inside and right turn -> rot left -> +1
|
||||
// Outside and right turn -> rot right -> -1
|
||||
float c_orient = (cross(t01, t12) > 0) ? 1 : -1;
|
||||
c_orient *= orientation;
|
||||
|
||||
bool fill_in = (c_orient > 0);
|
||||
fill_type = fill_in ? FILL_INSIDE : FILL_OUTSIDE;
|
||||
|
||||
// float orient = in_or_out * c_orient;
|
||||
|
||||
// Normal vectors
|
||||
// Rotate tangent vector 90-degrees clockwise
|
||||
// if the curve is positively oriented, otherwise
|
||||
// rotate it 90-degrees counterclockwise
|
||||
vec2 n01 = orientation * vec2(t01.y, -t01.x);
|
||||
vec2 n12 = orientation * vec2(t12.y, -t12.x);
|
||||
vec3 t01 = normalize(p1 - p0);
|
||||
vec3 t12 = normalize(p2 - p1);
|
||||
// Vectors perpendicular to the curve in the plane of the curve pointing outside the curve
|
||||
vec3 p0_perp = cross(t01, normal);
|
||||
vec3 p2_perp = cross(t12, normal);
|
||||
|
||||
bool fill_in = orientation > 0;
|
||||
float aaw = anti_alias_width;
|
||||
vec2 nudge1 = fill_in ? 0.5 * aaw * (n01 + n12) : vec2(0);
|
||||
vec2 corners[5] = vec2[5](
|
||||
bp0 + aaw * n01,
|
||||
bp0,
|
||||
bp1 + nudge1,
|
||||
bp2,
|
||||
bp2 + aaw * n12
|
||||
);
|
||||
vec3 corners[5];
|
||||
if(fill_in){
|
||||
// Note, straight lines will also fall into this case, and since p0_perp and p2_perp
|
||||
// will point to the right of the curve, it's just what we want
|
||||
corners = vec3[5](
|
||||
p0 + aaw * p0_perp,
|
||||
p0,
|
||||
p1 + 0.5 * aaw * (p0_perp + p2_perp),
|
||||
p2,
|
||||
p2 + aaw * p2_perp
|
||||
);
|
||||
}else{
|
||||
corners = vec3[5](
|
||||
p0,
|
||||
p0 - aaw * p0_perp,
|
||||
p1,
|
||||
p2 - aaw * p2_perp,
|
||||
p2
|
||||
);
|
||||
}
|
||||
|
||||
int coords_index_map[5] = int[5](0, 1, 2, 3, 4);
|
||||
if(!fill_in) coords_index_map = int[5](1, 0, 2, 4, 3);
|
||||
|
||||
mat3 xy_to_uv = get_xy_to_uv(bp0, bp1);
|
||||
mat3 xy_to_wz = get_xy_to_wz(bp0, bp1, bp2);
|
||||
uv_b2 = (xy_to_uv * vec3(bp2, 1)).xy;
|
||||
uv_anti_alias_width = anti_alias_width / length(bp1 - bp0);
|
||||
mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, normal);
|
||||
uv_b2 = (xyz_to_uv * vec4(p2, 1)).xy;
|
||||
uv_anti_alias_width = anti_alias_width / length(p1 - p0);
|
||||
|
||||
for(int i = 0; i < 5; i++){
|
||||
vec2 corner = corners[coords_index_map[i]];
|
||||
uv_coords = (xy_to_uv * vec3(corner, 1)).xy;
|
||||
wz_coords = (xy_to_wz * vec3(corner, 1)).xy;
|
||||
float z;
|
||||
// I haven't a clue why an index map doesn't work just
|
||||
// as well here, but for some reason it doesn't.
|
||||
if(i < 2){
|
||||
color = v_color[0];
|
||||
z = bp[0].z;
|
||||
}
|
||||
else if(i == 2){
|
||||
color = v_color[1];
|
||||
z = bp[1].z;
|
||||
}
|
||||
else{
|
||||
color = v_color[2];
|
||||
z = bp[2].z;
|
||||
}
|
||||
gl_Position = vec4(
|
||||
scale_and_shift_point_for_frame(vec3(corner, z)),
|
||||
1.0
|
||||
);
|
||||
EmitVertex();
|
||||
vec3 corner = corners[i];
|
||||
uv_coords = (xyz_to_uv * vec4(corner, 1)).xy;
|
||||
int j = int(sign(i - 1) + 1); // Maps i = [0, 1, 2, 3, 4] onto j = [0, 0, 1, 2, 2]
|
||||
emit_vertex_wrapper(corner, j);
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
||||
void main(){
|
||||
float fill_all = v_fill_all[0];
|
||||
fill_all = v_fill_all[0];
|
||||
vec3 local_unit_normal = get_unit_normal(vec3[3](bp[0], bp[1], bp[2]));
|
||||
orientation = sign(dot(v_global_unit_normal[0], local_unit_normal));
|
||||
|
||||
if(fill_all == 1){
|
||||
fill_type = FILL_ALL;
|
||||
emit_simple_triangle();
|
||||
}else{
|
||||
vec2 new_bp[3];
|
||||
int n = get_reduced_control_points(bp[0].xy, bp[1].xy, bp[2].xy, new_bp);
|
||||
bezier_degree = float(n);
|
||||
float orientation = v_orientation[0];
|
||||
|
||||
vec2 bp0, bp1, bp2;
|
||||
if(n == 0){
|
||||
return; // Don't emit any vertices
|
||||
}
|
||||
else if(n == 1){
|
||||
bp0 = new_bp[0];
|
||||
bp2 = new_bp[1];
|
||||
bp1 = 0.5 * (bp0 + bp2);
|
||||
}else{
|
||||
bp0 = new_bp[0];
|
||||
bp1 = new_bp[1];
|
||||
bp2 = new_bp[2];
|
||||
}
|
||||
|
||||
emit_pentagon(bp0, bp1, bp2, orientation);
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 new_bp[3];
|
||||
bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), new_bp);
|
||||
if(bezier_degree >= 1){
|
||||
emit_pentagon(new_bp, local_unit_normal);
|
||||
}
|
||||
// Don't emit any vertices for bezier_degree 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 to_screen_space;
|
||||
|
||||
in vec3 point;
|
||||
in vec3 unit_normal;
|
||||
in vec4 color;
|
||||
// fill_all is 0 or 1
|
||||
in float fill_all;
|
||||
// orientation is +1 for counterclockwise curves, -1 otherwise
|
||||
in float orientation;
|
||||
in float fill_all; // Either 0 or 1
|
||||
in float gloss;
|
||||
|
||||
out vec3 bp; // Bezier control point
|
||||
out vec3 v_global_unit_normal;
|
||||
out vec4 v_color;
|
||||
out float v_fill_all;
|
||||
out float v_orientation;
|
||||
|
||||
|
||||
#INSERT rotate_point_for_frame.glsl
|
||||
out float v_gloss;
|
||||
|
||||
// To my knowledge, there is no notion of #include for shaders,
|
||||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of named file
|
||||
#INSERT position_point_into_frame.glsl
|
||||
|
||||
void main(){
|
||||
bp = rotate_point_for_frame(point);
|
||||
bp = position_point_into_frame(point);
|
||||
v_global_unit_normal = normalize(position_point_into_frame(unit_normal));
|
||||
v_color = color;
|
||||
v_fill_all = fill_all;
|
||||
v_orientation = orientation;
|
||||
v_gloss = gloss;
|
||||
}
|
|
@ -1,26 +1,33 @@
|
|||
// This file is not a shader, it's just a set of
|
||||
// functions meant to be inserted into other shaders.
|
||||
|
||||
float cross(vec2 v, vec2 w){
|
||||
float cross2d(vec2 v, vec2 w){
|
||||
return v.x * w.y - w.x * v.y;
|
||||
}
|
||||
|
||||
// Matrix to convert to a uv space defined so that
|
||||
// Orthogonal matrix to convert to a uv space defined so that
|
||||
// b0 goes to [0, 0] and b1 goes to [1, 0]
|
||||
mat3 get_xy_to_uv(vec2 b0, vec2 b1){
|
||||
vec2 T = b1 - b0;
|
||||
|
||||
mat3 shift = mat3(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
-b0.x, -b0.y, 1
|
||||
mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){
|
||||
mat4 shift = mat4(
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
-b0.x, -b0.y, -b0.z, 1
|
||||
);
|
||||
mat3 rotate_and_scale = mat3(
|
||||
T.x, -T.y, 0,
|
||||
T.y, T.x, 0,
|
||||
0, 0, 1
|
||||
) / dot(T, T);
|
||||
return rotate_and_scale * shift;
|
||||
|
||||
float scale_factor = length(b1 - b0);
|
||||
vec3 I = (b1 - b0) / scale_factor;
|
||||
vec3 K = unit_normal;
|
||||
vec3 J = cross(K, I);
|
||||
// Transpose (hence inverse) of matrix taking
|
||||
// i-hat to I, k-hat to unit_normal, and j-hat to their cross
|
||||
mat4 rotate = mat4(
|
||||
I.x, J.x, K.x, 0,
|
||||
I.y, J.y, K.y, 0,
|
||||
I.z, J.z, K.z, 0,
|
||||
0, 0, 0 , 1
|
||||
);
|
||||
return (1 / scale_factor) * rotate * shift;
|
||||
}
|
||||
|
||||
|
||||
|
@ -29,31 +36,40 @@ mat3 get_xy_to_uv(vec2 b0, vec2 b1){
|
|||
// which for quadratics will be the same, but for linear and null
|
||||
// might change. The idea is to inform the caller of the degree,
|
||||
// while also passing tangency information in the linear case.
|
||||
int get_reduced_control_points(vec2 b0, vec2 b1, vec2 b2, out vec2 new_points[3]){
|
||||
float epsilon = 1e-6;
|
||||
vec2 v01 = (b1 - b0);
|
||||
vec2 v12 = (b2 - b1);
|
||||
bool distinct_01 = length(v01) > epsilon; // v01 is considered nonzero
|
||||
bool distinct_12 = length(v12) > epsilon; // v12 is considered nonzero
|
||||
// float get_reduced_control_points(vec3 b0, vec3 b1, vec3 b2, out vec3 new_points[3]){
|
||||
float get_reduced_control_points(in vec3 points[3], out vec3 new_points[3]){
|
||||
float length_threshold = 1e-6;
|
||||
float angle_threshold = 1e-3;
|
||||
|
||||
vec3 p0 = points[0];
|
||||
vec3 p1 = points[1];
|
||||
vec3 p2 = points[2];
|
||||
vec3 v01 = (p1 - p0);
|
||||
vec3 v12 = (p2 - p1);
|
||||
|
||||
float dot_prod = clamp(dot(normalize(v01), normalize(v12)), -1, 1);
|
||||
bool aligned = acos(dot_prod) < angle_threshold;
|
||||
bool distinct_01 = length(v01) > length_threshold; // v01 is considered nonzero
|
||||
bool distinct_12 = length(v12) > length_threshold; // v12 is considered nonzero
|
||||
int n_uniques = int(distinct_01) + int(distinct_12);
|
||||
if(n_uniques == 2){
|
||||
bool linear = dot(normalize(v01), normalize(v12)) > 1 - epsilon;
|
||||
if(linear){
|
||||
new_points[0] = b0;
|
||||
new_points[1] = b2;
|
||||
return 1;
|
||||
}else{
|
||||
new_points[0] = b0;
|
||||
new_points[1] = b1;
|
||||
new_points[2] = b2;
|
||||
return 2;
|
||||
}
|
||||
}else if(n_uniques == 1){
|
||||
new_points[0] = b0;
|
||||
new_points[1] = b2;
|
||||
return 1;
|
||||
|
||||
bool quadratic = (n_uniques == 2) && !aligned;
|
||||
bool linear = (n_uniques == 1) || ((n_uniques == 2) && aligned);
|
||||
bool constant = (n_uniques == 0);
|
||||
if(quadratic){
|
||||
new_points[0] = p0;
|
||||
new_points[1] = p1;
|
||||
new_points[2] = p2;
|
||||
return 2.0;
|
||||
}else if(linear){
|
||||
new_points[0] = p0;
|
||||
new_points[1] = (p0 + p2) / 2.0;
|
||||
new_points[2] = p2;
|
||||
return 1.0;
|
||||
}else{
|
||||
new_points[0] = b0;
|
||||
return 0;
|
||||
new_points[0] = p0;
|
||||
new_points[1] = p0;
|
||||
new_points[2] = p0;
|
||||
return 0.0;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,16 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 to_screen_space;
|
||||
uniform vec3 light_source_position;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
in vec3 global_unit_normal;
|
||||
in vec2 uv_coords;
|
||||
in vec2 uv_b2;
|
||||
|
||||
in float uv_stroke_width;
|
||||
in vec4 color;
|
||||
in float gloss;
|
||||
in float uv_anti_alias_width;
|
||||
|
||||
in float has_prev;
|
||||
|
@ -19,7 +25,7 @@ in float bezier_degree;
|
|||
out vec4 frag_color;
|
||||
|
||||
|
||||
float cross(vec2 v, vec2 w){
|
||||
float cross2d(vec2 v, vec2 w){
|
||||
return v.x * w.y - w.x * v.y;
|
||||
}
|
||||
|
||||
|
@ -64,8 +70,8 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
|||
);
|
||||
vec2 v21_unit = v21 / length(v21);
|
||||
float bevel_d = max(
|
||||
abs(cross(p - uv_b2, v21_unit)),
|
||||
abs(cross((rot * (p - uv_b2)), v21_unit))
|
||||
abs(cross2d(p - uv_b2, v21_unit)),
|
||||
abs(cross2d((rot * (p - uv_b2)), v21_unit))
|
||||
);
|
||||
return min(dist, bevel_d);
|
||||
}
|
||||
|
@ -78,14 +84,19 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
|||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of named file
|
||||
#INSERT quadratic_bezier_distance.glsl
|
||||
#INSERT add_light.glsl
|
||||
|
||||
|
||||
void main() {
|
||||
if (uv_stroke_width == 0) discard;
|
||||
|
||||
frag_color = color;
|
||||
float dist_to_curve = min_dist_to_curve(uv_coords, uv_b2, bezier_degree, false);
|
||||
// Add lighting if needed
|
||||
frag_color = add_light(color, xyz_coords, global_unit_normal, light_source_position, gloss);
|
||||
|
||||
float dist_to_curve = min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
|
||||
// An sdf for the region around the curve we wish to color.
|
||||
float signed_dist = abs(dist_to_curve) - 0.5 * uv_stroke_width;
|
||||
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width);
|
||||
|
||||
// frag_color.a += 0.3;
|
||||
}
|
|
@ -3,21 +3,24 @@
|
|||
layout (triangles) in;
|
||||
layout (triangle_strip, max_vertices = 5) out;
|
||||
|
||||
uniform float scale;
|
||||
// Needed for get_gl_Position
|
||||
uniform float aspect_ratio;
|
||||
uniform float focal_distance;
|
||||
uniform float anti_alias_width;
|
||||
uniform vec3 frame_center;
|
||||
|
||||
in vec3 bp[3];
|
||||
in vec3 prev_bp[3];
|
||||
in vec3 next_bp[3];
|
||||
in vec3 v_global_unit_normal[3];
|
||||
|
||||
in vec4 v_color[3];
|
||||
in float v_stroke_width[3];
|
||||
in float v_joint_type[3];
|
||||
in float v_gloss[3];
|
||||
|
||||
out vec4 color;
|
||||
out float uv_stroke_width;
|
||||
out float gloss;
|
||||
out float uv_anti_alias_width;
|
||||
|
||||
out float has_prev;
|
||||
|
@ -29,6 +32,8 @@ out float angle_to_next;
|
|||
|
||||
out float bezier_degree;
|
||||
|
||||
out vec3 xyz_coords;
|
||||
out vec3 global_unit_normal;
|
||||
out vec2 uv_coords;
|
||||
out vec2 uv_b2;
|
||||
|
||||
|
@ -43,36 +48,47 @@ const float MITER_JOINT = 3;
|
|||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of named file
|
||||
#INSERT quadratic_bezier_geometry_functions.glsl
|
||||
#INSERT scale_and_shift_point_for_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT get_unit_normal.glsl
|
||||
|
||||
|
||||
float angle_between_vectors(vec2 v1, vec2 v2){
|
||||
vec2 nv1 = normalize(v1);
|
||||
vec2 nv2 = normalize(v2);
|
||||
float get_aaw_scalar(vec3 normal){
|
||||
return min(abs(normal.z), 5);
|
||||
}
|
||||
|
||||
|
||||
float angle_between_vectors(vec3 v1, vec3 v2, vec3 normal){
|
||||
float v1_norm = length(v1);
|
||||
float v2_norm = length(v2);
|
||||
if(v1_norm == 0 || v2_norm == 0) return 0;
|
||||
vec3 nv1 = v1 / v1_norm;
|
||||
vec3 nv2 = v2 / v2_norm;
|
||||
// float signed_area = clamp(dot(cross(nv1, nv2), normal), -1, 1);
|
||||
// return asin(signed_area);
|
||||
float unsigned_angle = acos(clamp(dot(nv1, nv2), -1, 1));
|
||||
float sn = sign(cross(nv1, nv2));
|
||||
float sn = sign(dot(cross(nv1, nv2), normal));
|
||||
return sn * unsigned_angle;
|
||||
}
|
||||
|
||||
|
||||
bool find_intersection(vec2 p0, vec2 v0, vec2 p1, vec2 v1, out vec2 intersection){
|
||||
bool find_intersection(vec3 p0, vec3 v0, vec3 p1, vec3 v1, vec3 normal, out vec3 intersection){
|
||||
// Find the intersection of a line passing through
|
||||
// p0 in the direction v0 and one passing through p1 in
|
||||
// the direction p1.
|
||||
// That is, find a solutoin to p0 + v0 * t = p1 + v1 * s
|
||||
// float det = -v0.x * v1.y + v1.x * v0.y;
|
||||
float det = cross(v1, v0);
|
||||
float det = dot(cross(v1, v0), normal);
|
||||
if(det == 0){
|
||||
// intersection = p0;
|
||||
return false;
|
||||
}
|
||||
float t = cross(p0 - p1, v1) / det;
|
||||
float t = dot(cross(p0 - p1, v1), normal) / det;
|
||||
intersection = p0 + v0 * t;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool is_between(vec2 p, vec2 a, vec2 b){
|
||||
bool is_between(vec3 p, vec3 a, vec3 b){
|
||||
// Assumes three points fall on a line, returns whether
|
||||
// or not p sits between a and b.
|
||||
float d_pa = distance(p, a);
|
||||
|
@ -84,18 +100,18 @@ bool is_between(vec2 p, vec2 a, vec2 b){
|
|||
|
||||
// Tries to detect if one of the corners defined by the buffer around
|
||||
// b0 and b2 should be modified to form a better convex hull
|
||||
bool should_motify_corner(vec2 c, vec2 from_c, vec2 o1, vec2 o2, vec2 from_o, float buff){
|
||||
vec2 int1;
|
||||
vec2 int2;
|
||||
find_intersection(c, from_c, o1, from_o, int1);
|
||||
find_intersection(c, from_c, o2, from_o, int2);
|
||||
bool should_motify_corner(vec3 c, vec3 from_c, vec3 o1, vec3 o2, vec3 from_o, vec3 normal, float buff){
|
||||
vec3 int1;
|
||||
vec3 int2;
|
||||
find_intersection(c, from_c, o1, from_o, normal, int1);
|
||||
find_intersection(c, from_c, o2, from_o, normal, int2);
|
||||
return !is_between(int2, c + 1 * from_c * buff, int1);
|
||||
}
|
||||
|
||||
|
||||
void create_joint(float angle, vec2 unit_tan, float buff, float should_bevel,
|
||||
vec2 static_c0, out vec2 changing_c0,
|
||||
vec2 static_c1, out vec2 changing_c1){
|
||||
void create_joint(float angle, vec3 unit_tan, float buff, float should_bevel,
|
||||
vec3 static_c0, out vec3 changing_c0,
|
||||
vec3 static_c1, out vec3 changing_c1){
|
||||
float shift;
|
||||
float joint_type = v_joint_type[0];
|
||||
bool miter = (
|
||||
|
@ -119,122 +135,70 @@ void create_joint(float angle, vec2 unit_tan, float buff, float should_bevel,
|
|||
// This function is responsible for finding the corners of
|
||||
// a bounding region around the bezier curve, which can be
|
||||
// emitted as a triangle fan
|
||||
int get_corners(vec2 controls[3], int degree, out vec2 corners[5]){
|
||||
// Unit vectors for directions between
|
||||
// Various control points
|
||||
vec2 v02, v20, v10, v01, v12, v21;
|
||||
int get_corners(vec3 controls[3], vec3 normal, int degree, out vec3 corners[5]){
|
||||
vec3 p0 = controls[0];
|
||||
vec3 p1 = controls[1];
|
||||
vec3 p2 = controls[2];
|
||||
|
||||
vec2 p0 = controls[0];
|
||||
vec2 p2 = controls[degree];
|
||||
v02 = normalize(p2 - p0);
|
||||
v20 = -v02;
|
||||
if(degree == 2){
|
||||
v10 = normalize(p0 - controls[1]);
|
||||
v12 = normalize(p2 - controls[1]);
|
||||
}else{
|
||||
v10 = v20;
|
||||
v12 = v02;
|
||||
}
|
||||
v01 = -v10;
|
||||
v21 = -v12;
|
||||
// Unit vectors for directions between control points
|
||||
vec3 v10 = normalize(p0 - p1);
|
||||
vec3 v12 = normalize(p2 - p1);
|
||||
vec3 v01 = -v10;
|
||||
vec3 v21 = -v12;
|
||||
|
||||
// Find bounding points around ends
|
||||
vec2 p0_perp = vec2(-v01.y, v01.x);
|
||||
vec2 p2_perp = vec2(-v21.y, v21.x);
|
||||
//
|
||||
vec3 p0_perp = cross(normal, v01); // Pointing to the left of the curve from p0
|
||||
vec3 p2_perp = cross(normal, v12); // Pointing to the left of the curve from p2
|
||||
|
||||
float buff0 = 0.5 * v_stroke_width[0] + anti_alias_width;
|
||||
float buff2 = 0.5 * v_stroke_width[2] + anti_alias_width;
|
||||
float aaw0 = (1 - has_prev) * anti_alias_width;
|
||||
float aaw2 = (1 - has_next) * anti_alias_width;
|
||||
// aaw is the added width given around the polygon for antialiasing.
|
||||
// In case the normal is faced away from (0, 0, 1), the vector to the
|
||||
// camera, this is scaled up.
|
||||
float aaw = anti_alias_width / get_aaw_scalar(normal);
|
||||
float buff0 = 0.5 * v_stroke_width[0] + aaw;
|
||||
float buff2 = 0.5 * v_stroke_width[2] + aaw;
|
||||
float aaw0 = (1 - has_prev) * aaw;
|
||||
float aaw2 = (1 - has_next) * aaw;
|
||||
|
||||
vec2 c0 = p0 - buff0 * p0_perp + aaw0 * v10;
|
||||
vec2 c1 = p0 + buff0 * p0_perp + aaw0 * v10;
|
||||
vec2 c2 = p2 - p2_perp * buff2 + aaw2 * v12;
|
||||
vec2 c3 = p2 + p2_perp * buff2 + aaw2 * v12;
|
||||
vec3 c0 = p0 - buff0 * p0_perp + aaw0 * v10;
|
||||
vec3 c1 = p0 + buff0 * p0_perp + aaw0 * v10;
|
||||
vec3 c2 = p2 + buff2 * p2_perp + aaw2 * v12;
|
||||
vec3 c3 = p2 - buff2 * p2_perp + aaw2 * v12;
|
||||
|
||||
// Account for previous and next control points
|
||||
if(has_prev == 1){
|
||||
create_joint(angle_from_prev, v01, buff0, bevel_start, c0, c0, c1, c1);
|
||||
}
|
||||
if(has_next == 1){
|
||||
create_joint(-angle_to_next, v21, buff2, bevel_end, c2, c2, c3, c3);
|
||||
}
|
||||
if(has_prev > 0) create_joint(angle_from_prev, v01, buff0, bevel_start, c0, c0, c1, c1);
|
||||
if(has_next > 0) create_joint(angle_to_next, v21, buff2, bevel_end, c3, c3, c2, c2);
|
||||
|
||||
// Linear case is the simplets
|
||||
// Linear case is the simplest
|
||||
if(degree == 1){
|
||||
// Swap between 2 and 3 is deliberate, the order of corners
|
||||
// should be for a triangle_strip. Last entry is a dummy
|
||||
corners = vec2[5](c0, c1, c3, c2, vec2(0.0));
|
||||
corners = vec3[5](c0, c1, c3, c2, vec3(0.0));
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Some admitedly complicated logic to (hopefully efficiently)
|
||||
// make sure corners forms a convex hull around the curve.
|
||||
if(cross(v10, v12) > 0){
|
||||
bool change_c0 = (
|
||||
// has_prev == 0 &&
|
||||
dot(v21, v20) > 0 &&
|
||||
should_motify_corner(c0, v01, c2, c3, v21, buff0)
|
||||
);
|
||||
if(change_c0) c0 = p0 + p2_perp * buff0;
|
||||
|
||||
bool change_c3 = (
|
||||
// has_next == 0 &&
|
||||
dot(v01, v02) > 0 &&
|
||||
should_motify_corner(c3, v21, c1, c0, v01, buff2)
|
||||
);
|
||||
if(change_c3) c3 = p2 - p0_perp * buff2;
|
||||
|
||||
vec2 i12;
|
||||
find_intersection(c1, v01, c2, v21, i12);
|
||||
corners = vec2[5](c1, c0, i12, c3, c2);
|
||||
}else{
|
||||
bool change_c1 = (
|
||||
// has_prev == 0 &&
|
||||
dot(v21, v20) > 0 &&
|
||||
should_motify_corner(c1, v01, c3, c2, v21, buff0)
|
||||
);
|
||||
if(change_c1) c1 = p0 - p2_perp * buff0;
|
||||
|
||||
bool change_c2 = (
|
||||
// has_next == 0 &&
|
||||
dot(v01, v02) > 0 &&
|
||||
should_motify_corner(c2, v21, c0, c1, v01, buff2)
|
||||
);
|
||||
if(change_c2) c2 = p2 + p0_perp * buff2;
|
||||
|
||||
vec2 i03;
|
||||
find_intersection(c0, v01, c3, v21, i03);
|
||||
corners = vec2[5](c0, c1, i03, c2, c3);
|
||||
}
|
||||
// Otherwise, form a pentagon around the curve
|
||||
float orientation = sign(dot(cross(v01, v12), normal)); // Positive for ccw curves
|
||||
if(orientation > 0) corners = vec3[5](c0, c1, p1, c2, c3);
|
||||
else corners = vec3[5](c1, c0, p1, c3, c2);
|
||||
// Replace corner[2] with convex hull point accounting for stroke width
|
||||
find_intersection(corners[0], v01, corners[4], v21, normal, corners[2]);
|
||||
return 5;
|
||||
}
|
||||
|
||||
|
||||
void set_adjascent_info(vec2 c0, vec2 tangent,
|
||||
int degree, int mult, int flip,
|
||||
vec2 adj[3],
|
||||
out float has,
|
||||
void set_adjascent_info(vec3 c0, vec3 tangent,
|
||||
int degree,
|
||||
vec3 normal,
|
||||
vec3 adj[3],
|
||||
out float bevel,
|
||||
out float angle
|
||||
){
|
||||
float joint_type = v_joint_type[0];
|
||||
|
||||
has = 0;
|
||||
bevel = 0;
|
||||
angle = 0;
|
||||
|
||||
vec2 new_adj[3];
|
||||
int adj_degree = get_reduced_control_points(
|
||||
adj[0], adj[1], adj[2], new_adj
|
||||
);
|
||||
has = float(adj_degree > 0);
|
||||
if(has == 1){
|
||||
vec2 adj = new_adj[mult * adj_degree - flip];
|
||||
angle = flip * angle_between_vectors(c0 - adj, tangent);
|
||||
}
|
||||
vec3 new_adj[3];
|
||||
float adj_degree = get_reduced_control_points(adj, new_adj);
|
||||
// Check if adj_degree is zero?
|
||||
angle = angle_between_vectors(c0 - new_adj[1], tangent, normal);
|
||||
// Decide on joint type
|
||||
bool one_linear = (degree == 1 || adj_degree == 1);
|
||||
bool one_linear = (degree == 1 || adj_degree == 1.0);
|
||||
bool should_bevel = (
|
||||
(joint_type == AUTO_JOINT && one_linear) ||
|
||||
joint_type == BEVEL_JOINT
|
||||
|
@ -243,73 +207,68 @@ void set_adjascent_info(vec2 c0, vec2 tangent,
|
|||
}
|
||||
|
||||
|
||||
void set_previous_and_next(vec2 controls[3], int degree){
|
||||
float a_tol = 1e-10;
|
||||
void set_previous_and_next(vec3 controls[3], int degree, vec3 normal){
|
||||
float a_tol = 1e-8;
|
||||
|
||||
if(distance(prev_bp[2], bp[0]) < a_tol){
|
||||
vec2 tangent = controls[1] - controls[0];
|
||||
// Made as floats not bools so they can be passed to the frag shader
|
||||
has_prev = float(distance(prev_bp[2], bp[0]) < a_tol);
|
||||
has_next = float(distance(next_bp[0], bp[2]) < a_tol);
|
||||
|
||||
if(has_prev > 0){
|
||||
vec3 tangent = controls[1] - controls[0];
|
||||
set_adjascent_info(
|
||||
controls[0], tangent, degree, 1, 1,
|
||||
vec2[3](prev_bp[0].xy, prev_bp[1].xy, prev_bp[2].xy),
|
||||
has_prev, bevel_start, angle_from_prev
|
||||
controls[0], tangent, degree, normal,
|
||||
vec3[3](prev_bp[0], prev_bp[1], prev_bp[2]),
|
||||
bevel_start, angle_from_prev
|
||||
);
|
||||
}
|
||||
if(distance(next_bp[0], bp[2]) < a_tol){
|
||||
vec2 tangent = controls[degree - 1] - controls[degree];
|
||||
if(has_next > 0){
|
||||
vec3 tangent = controls[1] - controls[2];
|
||||
set_adjascent_info(
|
||||
controls[degree], tangent, degree, 0, -1,
|
||||
vec2[3](next_bp[0].xy, next_bp[1].xy, next_bp[2].xy),
|
||||
has_next, bevel_end, angle_to_next
|
||||
controls[2], tangent, degree, normal,
|
||||
vec3[3](next_bp[0], next_bp[1], next_bp[2]),
|
||||
bevel_end, angle_to_next
|
||||
);
|
||||
angle_to_next *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
vec2 controls[3];
|
||||
int degree = get_reduced_control_points(bp[0].xy, bp[1].xy, bp[2].xy, controls);
|
||||
bezier_degree = float(degree);
|
||||
vec3 unit_normal = v_global_unit_normal[0];
|
||||
// anti_alias_width /= cos(0.5 * acos(abs(unit_normal.z)));
|
||||
|
||||
// Null curve or linear with higher index than needed
|
||||
vec3 controls[3];
|
||||
bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), controls);
|
||||
int degree = int(bezier_degree);
|
||||
|
||||
// Null curve
|
||||
if(degree == 0) return;
|
||||
|
||||
set_previous_and_next(controls, degree);
|
||||
set_previous_and_next(controls, degree, unit_normal);
|
||||
|
||||
// Find uv conversion matrix
|
||||
mat3 xy_to_uv = get_xy_to_uv(controls[0], controls[1]);
|
||||
mat4 xyz_to_uv = get_xyz_to_uv(controls[0], controls[1], unit_normal);
|
||||
float scale_factor = length(controls[1] - controls[0]);
|
||||
uv_anti_alias_width = anti_alias_width / scale_factor;
|
||||
uv_b2 = (xy_to_uv * vec3(controls[degree], 1.0)).xy;
|
||||
uv_anti_alias_width = anti_alias_width / scale_factor / get_aaw_scalar(unit_normal);
|
||||
uv_b2 = (xyz_to_uv * vec4(controls[2], 1.0)).xy;
|
||||
|
||||
// Corners of a bounding region around curve
|
||||
vec2 corners[5];
|
||||
int n_corners = get_corners(controls, degree, corners);
|
||||
vec3 corners[5];
|
||||
int n_corners = get_corners(controls, unit_normal, degree, corners);
|
||||
|
||||
// Get style info aligned to the corners
|
||||
float stroke_widths[5];
|
||||
vec4 stroke_colors[5];
|
||||
float z_values[5];
|
||||
int index_map[5];
|
||||
if(n_corners == 4) index_map = int[5](0, 0, 2, 2, 2);
|
||||
else index_map = int[5](0, 0, 1, 2, 2);
|
||||
for(int i = 0; i < 5; i++){
|
||||
stroke_widths[i] = v_stroke_width[index_map[i]];
|
||||
stroke_colors[i] = v_color[index_map[i]];
|
||||
z_values[i] = bp[index_map[i]].z; // TODO, seems clunky
|
||||
}
|
||||
int index_map[5] = int[5](0, 0, 1, 2, 2);
|
||||
if(n_corners == 4) index_map[2] = 2;
|
||||
|
||||
// Emit each corner
|
||||
for(int i = 0; i < n_corners; i++){
|
||||
vec2 corner = corners[i];
|
||||
uv_coords = (xy_to_uv * vec3(corner, 1.0)).xy;
|
||||
|
||||
uv_stroke_width = stroke_widths[i] / scale_factor;
|
||||
color = stroke_colors[i];
|
||||
|
||||
gl_Position = vec4(
|
||||
scale_and_shift_point_for_frame(vec3(corner, z_values[i])),
|
||||
1.0
|
||||
);
|
||||
xyz_coords = corners[i];
|
||||
uv_coords = (xyz_to_uv * vec4(xyz_coords, 1.0)).xy;
|
||||
uv_stroke_width = v_stroke_width[index_map[i]] / scale_factor;
|
||||
color = v_color[index_map[i]];
|
||||
gloss = v_gloss[index_map[i]];
|
||||
global_unit_normal = v_global_unit_normal[index_map[i]];
|
||||
gl_Position = get_gl_Position(xyz_coords);
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
|
|
|
@ -1,34 +1,44 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float focal_distance;
|
||||
|
||||
in vec3 point;
|
||||
in vec3 prev_point;
|
||||
in vec3 next_point;
|
||||
in vec3 unit_normal;
|
||||
|
||||
in float stroke_width;
|
||||
in vec4 color;
|
||||
in float joint_type;
|
||||
in float gloss;
|
||||
|
||||
out vec3 bp; // Bezier control point
|
||||
// Bezier control point
|
||||
out vec3 bp;
|
||||
out vec3 prev_bp;
|
||||
out vec3 next_bp;
|
||||
out vec3 v_global_unit_normal;
|
||||
|
||||
out float v_stroke_width;
|
||||
out vec4 v_color;
|
||||
out float v_joint_type;
|
||||
out float v_gloss;
|
||||
|
||||
// TODO, this should maybe depend on scale
|
||||
const float STROKE_WIDTH_CONVERSION = 0.01;
|
||||
|
||||
|
||||
#INSERT rotate_point_for_frame.glsl
|
||||
const float STROKE_WIDTH_CONVERSION = 0.0025;
|
||||
|
||||
// To my knowledge, there is no notion of #include for shaders,
|
||||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of named file
|
||||
#INSERT position_point_into_frame.glsl
|
||||
|
||||
void main(){
|
||||
bp = position_point_into_frame(point);
|
||||
prev_bp = position_point_into_frame(prev_point);
|
||||
next_bp = position_point_into_frame(next_point);
|
||||
v_global_unit_normal = normalize(position_point_into_frame(unit_normal));
|
||||
|
||||
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width;
|
||||
v_color = color;
|
||||
v_joint_type = joint_type;
|
||||
|
||||
bp = rotate_point_for_frame(point);
|
||||
prev_bp = rotate_point_for_frame(prev_point);
|
||||
next_bp = rotate_point_for_frame(next_point);
|
||||
v_gloss = gloss;
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
vec3 rotate_point_for_frame(vec3 point){
|
||||
// TODO, orient in 3d based on certain rotation matrices
|
||||
return point;
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
// Assumes theese uniforms exist in the surrounding context
|
||||
// uniform float scale;
|
||||
// Assumes the following uniforms exist in the surrounding context:
|
||||
// uniform float aspect_ratio;
|
||||
// uniform float frame_center;
|
||||
// TODO, rename
|
||||
|
||||
vec3 scale_and_shift_point_for_frame(vec3 point){
|
||||
point -= frame_center;
|
||||
point /= scale;
|
||||
vec3 get_gl_Position(vec3 point){
|
||||
point.x /= aspect_ratio;
|
||||
return point;
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
#version 330
|
||||
|
||||
uniform float scale;
|
||||
uniform float aspect_ratio;
|
||||
uniform float anti_alias_width;
|
||||
uniform vec3 frame_center;
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float focal_distance;
|
||||
|
||||
in vec3 point;
|
||||
|
||||
// Analog of import for manim only
|
||||
#INSERT set_gl_Position.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT position_point_into_frame.glsl
|
||||
|
||||
void main(){
|
||||
set_gl_Position(point);
|
||||
gl_Position = get_gl_Position(position_point_into_frame(point));
|
||||
}
|
12
manimlib/shaders/surface_frag.glsl
Normal file
12
manimlib/shaders/surface_frag.glsl
Normal file
|
@ -0,0 +1,12 @@
|
|||
#version 330
|
||||
|
||||
// uniform sampler2D Texture;
|
||||
|
||||
// in vec2 v_im_coords;
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
frag_color = v_color;
|
||||
}
|
31
manimlib/shaders/surface_vert.glsl
Normal file
31
manimlib/shaders/surface_vert.glsl
Normal file
|
@ -0,0 +1,31 @@
|
|||
#version 330
|
||||
|
||||
uniform float aspect_ratio;
|
||||
uniform float anti_alias_width;
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float focal_distance;
|
||||
uniform vec3 light_source_position;
|
||||
|
||||
// uniform sampler2D Texture;
|
||||
|
||||
in vec3 point;
|
||||
in vec3 normal;
|
||||
// in vec2 im_coords;
|
||||
in vec4 color;
|
||||
in float gloss;
|
||||
|
||||
// out vec2 v_im_coords;
|
||||
out vec4 v_color;
|
||||
|
||||
// Analog of import for manim only
|
||||
#INSERT position_point_into_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT add_light.glsl
|
||||
|
||||
void main(){
|
||||
vec3 xyz_coords = position_point_into_frame(point);
|
||||
vec3 unit_normal = normalize(position_point_into_frame(normal));
|
||||
// v_im_coords = im_coords;
|
||||
v_color = add_light(color, xyz_coords, unit_normal, light_source_position, gloss);
|
||||
gl_Position = get_gl_Position(xyz_coords);
|
||||
}
|
|
@ -3,9 +3,10 @@ import math
|
|||
import itertools as it
|
||||
from mapbox_earcut import triangulate_float32 as earcut
|
||||
|
||||
from manimlib.constants import RIGHT
|
||||
from manimlib.constants import UP
|
||||
from manimlib.constants import OUT
|
||||
from manimlib.constants import PI
|
||||
from manimlib.constants import RIGHT
|
||||
from manimlib.constants import TAU
|
||||
from manimlib.utils.iterables import adjacent_pairs
|
||||
|
||||
|
@ -84,6 +85,22 @@ def thick_diagonal(dim, thickness=2):
|
|||
return (np.abs(row_indices - col_indices) < thickness).astype('uint8')
|
||||
|
||||
|
||||
def rotation_matrix_transpose_from_quaternion(quat):
|
||||
quat_inv = quaternion_conjugate(quat)
|
||||
return [
|
||||
quaternion_mult(quat, [0, *basis], quat_inv)[1:]
|
||||
for basis in [
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
def rotation_matrix_from_quaternion(quat):
|
||||
return np.transpose(rotation_matrix_transpose_from_quaternion(quat))
|
||||
|
||||
|
||||
def rotation_matrix_transpose(angle, axis):
|
||||
if axis[0] == 0 and axis[1] == 0:
|
||||
# axis = [0, 0, z] case is common enough it's worth
|
||||
|
@ -97,15 +114,7 @@ def rotation_matrix_transpose(angle, axis):
|
|||
[0, 0, 1],
|
||||
]
|
||||
quat = quaternion_from_angle_axis(angle, axis)
|
||||
quat_inv = quaternion_conjugate(quat)
|
||||
return [
|
||||
quaternion_mult(quat, [0, *basis], quat_inv)[1:]
|
||||
for basis in [
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
]
|
||||
]
|
||||
return rotation_matrix_transpose_from_quaternion(quat)
|
||||
|
||||
|
||||
def rotation_matrix(angle, axis):
|
||||
|
@ -128,25 +137,11 @@ def z_to_vector(vector):
|
|||
Returns some matrix in SO(3) which takes the z-axis to the
|
||||
(normalized) vector provided as an argument
|
||||
"""
|
||||
norm = get_norm(vector)
|
||||
if norm == 0:
|
||||
cp = cross(OUT, vector)
|
||||
if get_norm(cp) == 0:
|
||||
return np.identity(3)
|
||||
v = np.array(vector) / norm
|
||||
phi = np.arccos(v[2])
|
||||
if any(v[:2]):
|
||||
# projection of vector to unit circle
|
||||
axis_proj = v[:2] / get_norm(v[:2])
|
||||
theta = np.arccos(axis_proj[0])
|
||||
if axis_proj[1] < 0:
|
||||
theta = -theta
|
||||
else:
|
||||
theta = 0
|
||||
phi_down = np.array([
|
||||
[math.cos(phi), 0, math.sin(phi)],
|
||||
[0, 1, 0],
|
||||
[-math.sin(phi), 0, math.cos(phi)]
|
||||
])
|
||||
return np.dot(rotation_about_z(theta), phi_down)
|
||||
angle = np.arccos(np.dot(OUT, normalize(vector)))
|
||||
return rotation_matrix(angle, axis=cp)
|
||||
|
||||
|
||||
def angle_of_vector(vector):
|
||||
|
@ -188,8 +183,19 @@ def cross(v1, v2):
|
|||
])
|
||||
|
||||
|
||||
def get_unit_normal(v1, v2):
|
||||
return normalize(cross(v1, v2))
|
||||
def get_unit_normal(v1, v2, tol=1e-6):
|
||||
v1 = normalize(v1)
|
||||
v2 = normalize(v2)
|
||||
cp = cross(v1, v2)
|
||||
cp_norm = get_norm(cp)
|
||||
if cp_norm < tol:
|
||||
# Vectors align, so find a normal to them in the plane shared with the z-axis
|
||||
new_cp = cross(cross(v1, OUT), v1)
|
||||
new_cp_norm = get_norm(new_cp)
|
||||
if new_cp_norm < tol:
|
||||
return UP
|
||||
return new_cp / new_cp_norm
|
||||
return cp / cp_norm
|
||||
|
||||
|
||||
###
|
||||
|
|
Loading…
Add table
Reference in a new issue