Merge pull request #6 from 3b1b/shaders

update shaders branch
This commit is contained in:
鹤翔万里 2020-06-05 08:20:26 +08:00 committed by GitHub
commit 0f998615ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 6857 additions and 2672 deletions

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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()

View 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",
],
}

View file

@ -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)

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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 *

View file

@ -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,

View file

@ -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,

View file

@ -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

View file

@ -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)

View file

@ -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)
)

View file

@ -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)
)

View file

@ -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)

View 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

View file

@ -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

View file

@ -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":

View file

@ -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)

View file

@ -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,

View 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
);
}

View 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);
}

View 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;
}

View file

@ -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));
}

View 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;
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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();

View file

@ -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;
}

View file

@ -1,4 +0,0 @@
vec3 rotate_point_for_frame(vec3 point){
// TODO, orient in 3d based on certain rotation matrices
return point;
}

View file

@ -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;
}

View file

@ -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));
}

View 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;
}

View 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);
}

View file

@ -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
###