Drop redundant OpenGL files and add metaclass support for Surface (#1843)

* replace old with new

* dropped redundant files

* replace examples in example_Scenes

* replace opengl_ in test

* ammend __init__

* opengl_compatibilty for Surface

* make resolution a tuple for opengl compat

* resolution changes in opengl.py

* rework sphere and enable dual renderer for 3d shapes

* adjust res for Torus

* actually drop file

* flip cairo_sphere resolution

* changes

* render each submobject individually

* make BraceLabel appear

* remove if config.renderer==opengl from mobject.py

* rewrite tests

* properly deprecate ParametricSurface

* revert rendering each submobject individually

* initialize OpenGLRenderer in Scene if config.renderer is opengl

* fix bases when config.renderer is changed

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* actually push test

* remove unused bounding_box + data from mobject.py

* overwrite tests

* use #1899 to switch renderer at runtime to make tests work

* attempt at fixing tests

* add renderer=cairo at the end of the test??

* rewrite failing test

* remove self.attr = self.attr for bracelabel

* remove accidental line in test

* remove unecessary bloat and make set_fill_by_value dual compatible

* update to remove deprecation warning

* add ParametricSurface to __all__

* deprecate ParametricSurface

* change some examples

* adjust docs

Co-authored-by: Darylgolden <darylgolden@gmail.com>

Co-authored-by: Benjamin Hackl <devel@benjamin-hackl.at>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Darylgolden <darylgolden@gmail.com>
This commit is contained in:
Laith Bahodi 2021-08-30 16:35:25 -04:00 committed by GitHub
commit 223c2f86d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 203 additions and 351 deletions

View file

@ -518,13 +518,13 @@ Special Camera Settings
.. manim:: ThreeDLightSourcePosition
:save_last_frame:
:ref_classes: ThreeDScene ThreeDAxes ParametricSurface
:ref_classes: ThreeDScene ThreeDAxes Surface
:ref_methods: ThreeDScene.set_camera_orientation
class ThreeDLightSourcePosition(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
sphere = ParametricSurface(
sphere = Surface(
lambda u, v: np.array([
1.5 * np.cos(u) * np.cos(v),
1.5 * np.cos(u) * np.sin(v),
@ -569,7 +569,7 @@ Special Camera Settings
.. manim:: ThreeDSurfacePlot
:save_last_frame:
:ref_classes: ThreeDScene ParametricSurface
:ref_classes: ThreeDScene Surface
class ThreeDSurfacePlot(ThreeDScene):
def construct(self):
@ -584,7 +584,7 @@ Special Camera Settings
z = np.exp(-(d ** 2 / (2.0 * sigma ** 2)))
return np.array([x, y, z])
gauss_plane = ParametricSurface(
gauss_plane = Surface(
param_gauss,
resolution=(resolution_fa, resolution_fa),
v_range=[-2, +2],

View file

@ -75,11 +75,9 @@ class TextTest(Scene):
def construct(self):
import string
text = OpenGLText(
string.ascii_lowercase, stroke_width=4, stroke_color=BLUE
).scale(2)
text = Text(string.ascii_lowercase, stroke_width=4, stroke_color=BLUE).scale(2)
text2 = (
OpenGLText(string.ascii_uppercase, stroke_width=4, stroke_color=BLUE)
Text(string.ascii_uppercase, stroke_width=4, stroke_color=BLUE)
.scale(2)
.next_to(text, DOWN)
)
@ -136,10 +134,10 @@ class ThreeDMobjectTest(Scene):
def construct(self):
# config["background_color"] = "#333333"
s = OpenGLSquare(fill_opacity=0.5).shift(2 * RIGHT)
s = Square(fill_opacity=0.5).shift(2 * RIGHT)
self.add(s)
sp = OpenGLSphere().shift(2 * LEFT)
sp = Sphere().shift(2 * LEFT)
self.add(sp)
mesh = get_plane_mesh(self.renderer.context)
@ -288,7 +286,7 @@ class InlineShaderExample(Scene):
def construct(self):
config["background_color"] = "#333333"
c = OpenGLCircle(fill_opacity=0.7).shift(UL)
c = Circle(fill_opacity=0.7).shift(UL)
self.add(c)
shader = Shader(
@ -395,10 +393,10 @@ class NamedShaderExample(Scene):
class InteractiveDevelopment(Scene):
def construct(self):
circle = OpenGLCircle()
circle = Circle()
circle.set_fill(BLUE, opacity=0.5)
circle.set_stroke(BLUE_E, width=4)
square = OpenGLSquare()
square = Square()
self.play(Create(square))
self.wait()
@ -449,9 +447,9 @@ class SurfaceExample(Scene):
# self.add(surface_text)
# self.wait(0.1)
torus1 = OpenGLTorus(r1=1, r2=1)
torus2 = OpenGLTorus(r1=3, r2=1)
sphere = OpenGLSphere(radius=3, resolution=torus1.resolution)
torus1 = Torus(major_radius=1, minor_radius=1)
torus2 = Torus(major_radius=3, minor_radius=1)
sphere = Sphere(radius=3, resolution=torus1.resolution)
# You can texture a surface with up to two images, which will
# be interpreted as the side towards the light, and away from
# the light. These can be either urls, or paths to a local file

View file

@ -107,20 +107,6 @@ class Mobject:
self.generate_points()
self.init_colors()
# OpenGL data.
self.data = {}
self.depth_test = False
self.is_fixed_in_frame = False
self.gloss = 0.0
self.shadow = 0.0
self.needs_new_bounding_box = True
self.parents = []
self.family = [self]
self.init_gl_data()
self.init_gl_points()
self.init_gl_colors()
@classmethod
def animation_override_for(
cls, animation_class: Type["Animation"]
@ -191,41 +177,6 @@ class Mobject:
f"{override_func.__qualname__}."
)
def init_gl_data(self):
pass
def init_gl_points(self):
pass
def init_gl_colors(self):
pass
def get_bounding_box(self):
if self.needs_new_bounding_box:
self.data["bounding_box"] = self.compute_bounding_box()
self.needs_new_bounding_box = False
return self.data["bounding_box"]
def compute_bounding_box(self):
all_points = np.vstack(
[
self.data["points"],
*(
mob.get_bounding_box()
for mob in self.get_family()[1:]
if mob.has_points()
),
]
)
if len(all_points) == 0:
return np.zeros((3, self.dim))
else:
# Lower left and upper right corners
mins = all_points.min(0)
maxs = all_points.max(0)
mids = (mins + maxs) / 2
return np.array([mins, mids, maxs])
@property
def animate(self):
"""Used to animate the application of a method.
@ -349,14 +300,6 @@ class Mobject:
"""
pass
def refresh_bounding_box(self, recurse_down=False, recurse_up=True):
for mob in self.get_family(recurse_down):
mob.needs_new_bounding_box = True
if recurse_up:
for parent in self.parents:
parent.refresh_bounding_box()
return self
def add(self, *mobjects: "Mobject") -> "Mobject":
"""Add mobjects as submobjects.
@ -416,24 +359,13 @@ class Mobject:
ValueError: Mobject cannot contain self
"""
if config.renderer == "opengl":
if self in mobjects:
raise Exception("Mobject cannot contain self")
for mobject in mobjects:
if mobject not in self.submobjects:
self.submobjects.append(mobject)
if self not in mobject.parents:
mobject.parents.append(self)
self.assemble_family()
return self
else:
for m in mobjects:
if not isinstance(m, Mobject):
raise TypeError("All submobjects must be of type Mobject")
if m is self:
raise ValueError("Mobject cannot contain self")
self.submobjects = list_update(self.submobjects, mobjects)
return self
for m in mobjects:
if not isinstance(m, Mobject):
raise TypeError("All submobjects must be of type Mobject")
if m is self:
raise ValueError("Mobject cannot contain self")
self.submobjects = list_update(self.submobjects, mobjects)
return self
def __add__(self, mobject):
raise NotImplementedError
@ -1127,21 +1059,12 @@ class Mobject:
:meth:`move_to`
"""
if config.renderer == "opengl":
self.apply_points_function(
lambda points: points + vectors[0],
about_edge=None,
works_on_bounding_box=True,
)
return self
else:
total_vector = reduce(op.add, vectors)
for mob in self.family_members_with_points():
mob.points = mob.points.astype("float")
mob.points += total_vector
if hasattr(mob, "data") and "points" in mob.data:
mob.data["points"] += total_vector
return self
total_vector = reduce(op.add, vectors)
for mob in self.family_members_with_points():
mob.points = mob.points.astype("float")
mob.points += total_vector
return self
def scale(self, scale_factor: float, **kwargs) -> "Mobject":
r"""Scale the size by a factor.
@ -1184,18 +1107,10 @@ class Mobject:
:meth:`move_to`
"""
if config.renderer == "opengl":
self.apply_points_function(
lambda points: scale_factor * points,
works_on_bounding_box=True,
**kwargs,
)
return self
else:
self.apply_points_function_about_point(
lambda points: scale_factor * points, **kwargs
)
return self
self.apply_points_function_about_point(
lambda points: scale_factor * points, **kwargs
)
return self
def rotate_about_origin(self, angle, axis=OUT, axes=[]):
"""Rotates the :class:`~.Mobject` about the ORIGIN, which is at [0,0,0]."""
@ -1209,18 +1124,11 @@ class Mobject:
**kwargs,
):
"""Rotates the :class:`~.Mobject` about a certain point."""
if config.renderer == "opengl":
rot_matrix_T = rotation_matrix_transpose(angle, axis)
self.apply_points_function(
lambda points: np.dot(points, rot_matrix_T), about_point, **kwargs
)
return self
else:
rot_matrix = rotation_matrix(angle, axis)
self.apply_points_function_about_point(
lambda points: np.dot(points, rot_matrix.T), about_point, **kwargs
)
return self
rot_matrix = rotation_matrix(angle, axis)
self.apply_points_function_about_point(
lambda points: np.dot(points, rot_matrix.T), about_point, **kwargs
)
return self
def flip(self, axis=UP, **kwargs):
"""Flips/Mirrors an mobject about its center.
@ -1343,31 +1251,6 @@ class Mobject:
# In place operations.
# Note, much of these are now redundant with default behavior of
# above methods
def apply_points_function(
self, func, about_point=None, about_edge=ORIGIN, works_on_bounding_box=False
):
if about_point is None and about_edge is not None:
about_point = self.get_bounding_box_point(about_edge)
for mob in self.get_family():
arrs = []
if len(self.data["points"]):
arrs.append(mob.data["points"])
if works_on_bounding_box:
arrs.append(mob.get_bounding_box())
for arr in arrs:
if about_point is None:
arr[:] = func(arr)
else:
arr[:] = func(arr - about_point) + about_point
if not works_on_bounding_box:
self.refresh_bounding_box(recurse_down=True)
else:
for parent in self.parents:
parent.refresh_bounding_box()
return self
def apply_points_function_about_point(
self, func, about_point=None, about_edge=None
@ -1888,10 +1771,7 @@ class Mobject:
return self.get_all_points()
def get_num_points(self):
if config.renderer == "opengl":
return len(self.data["points"])
else:
return len(self.points)
return len(self.points)
def get_extremum_along_dim(self, points=None, dim=0, key=0):
if points is None:
@ -2023,18 +1903,12 @@ class Mobject:
def get_start(self):
"""Returns the point, where the stroke that surrounds the :class:`~.Mobject` starts."""
self.throw_error_if_no_points()
if config.renderer == "opengl":
return np.array(self.data["points"][0])
else:
return np.array(self.points[0])
return np.array(self.points[0])
def get_end(self):
"""Returns the point, where the stroke that surrounds the :class:`~.Mobject` ends."""
self.throw_error_if_no_points()
if config.renderer == "opengl":
return np.array(self.data["points"][-1])
else:
return np.array(self.points[-1])
return np.array(self.points[-1])
def get_start_and_end(self):
"""Returns starting and ending point of a stroke as a ``tuple``."""
@ -2158,15 +2032,9 @@ class Mobject:
return result + self.submobjects
def get_family(self, recurse=True):
if config.renderer == "opengl":
if recurse:
return self.family
else:
return [self]
else:
sub_families = list(map(Mobject.get_family, self.submobjects))
all_mobjects = [self] + list(it.chain(*sub_families))
return remove_list_redundancies(all_mobjects)
sub_families = list(map(Mobject.get_family, self.submobjects))
all_mobjects = [self] + list(it.chain(*sub_families))
return remove_list_redundancies(all_mobjects)
def family_members_with_points(self):
return [m for m in self.get_family() if m.get_num_points() > 0]
@ -2624,12 +2492,7 @@ class Mobject:
self.add(dotL, dotR, dotMiddle)
"""
if config.renderer == "opengl":
self.data["points"][:] = path_func(
mobject1.data["points"], mobject2.data["points"], alpha
)
else:
self.points = path_func(mobject1.points, mobject2.points, alpha)
self.points = path_func(mobject1.points, mobject2.points, alpha)
self.interpolate_color(mobject1, mobject2, alpha)
return self
@ -2683,18 +2546,11 @@ class Mobject:
# Errors
def throw_error_if_no_points(self):
if config.renderer == "opengl":
if len(self.data["points"]) == 0:
caller_name = sys._getframe(1).f_code.co_name
raise Exception(
f"Cannot call Mobject.{caller_name} for a Mobject with no points"
)
else:
if self.has_no_points():
caller_name = sys._getframe(1).f_code.co_name
raise Exception(
f"Cannot call Mobject.{caller_name} for a Mobject with no points"
)
if self.has_no_points():
caller_name = sys._getframe(1).f_code.co_name
raise Exception(
f"Cannot call Mobject.{caller_name} for a Mobject with no points"
)
# About z-index
def set_z_index(

View file

@ -2,6 +2,7 @@ from abc import ABCMeta
from .. import config
from .opengl_mobject import OpenGLMobject
from .opengl_three_dimensions import OpenGLSurface
from .types.opengl_vectorized_mobject import OpenGLVMobject
@ -24,6 +25,7 @@ class ConvertToOpenGL(ABCMeta):
base_names_to_opengl = {
"Mobject": OpenGLMobject,
"VMobject": OpenGLVMobject,
"Surface": OpenGLSurface,
}
bases = tuple(

View file

@ -12,7 +12,7 @@ from colour import Color
from .. import config
from ..constants import *
from ..utils.bezier import interpolate
from ..utils.bezier import integer_interpolate, interpolate
from ..utils.color import *
from ..utils.config_ops import _Data, _Uniforms
@ -275,6 +275,9 @@ class OpenGLMobject:
mob.data[key] = mob.data[key][::-1]
return self
def get_midpoint(self):
return self.point_from_proportion(0.5)
def apply_points_function(
self, func, about_point=None, about_edge=ORIGIN, works_on_bounding_box=False
):

View file

@ -51,32 +51,3 @@ class OpenGLSurfaceMesh(OpenGLVGroup):
path = OpenGLVMobject()
path.set_points_smoothly(nudged_points[vi::full_nv])
self.add(path)
class OpenGLSphere(OpenGLSurface):
def __init__(self, resolution=None, radius=1, u_range=None, v_range=None, **kwargs):
resolution = resolution if resolution is not None else (101, 51)
u_range = u_range if u_range is not None else (0, TAU)
v_range = v_range if v_range is not None else (0, PI)
self.radius = radius
super().__init__(
resolution=resolution, u_range=u_range, v_range=v_range, **kwargs
)
def uv_func(self, u, v):
return self.radius * np.array(
[np.cos(u) * np.sin(v), np.sin(u) * np.sin(v), -np.cos(v)]
)
class OpenGLTorus(OpenGLSurface):
def __init__(self, u_range=None, v_range=None, r1=3, r2=1, **kwargs):
u_range = u_range if u_range is not None else (0, TAU)
v_range = v_range if v_range is not None else (0, TAU)
self.r1 = r1
self.r2 = r2
super().__init__(u_range=u_range, v_range=v_range, **kwargs)
def uv_func(self, u, v):
P = np.array([math.cos(u), math.sin(u), 0])
return (self.r1 - self.r2 * math.cos(v)) * P - math.sin(v) * OUT

View file

@ -90,7 +90,7 @@ class Polyhedron(VGroup):
faces_config: Dict[str, Union[str, int, float, bool]] = {},
graph_config: Dict[str, Union[str, int, float, bool]] = {},
):
VGroup.__init__(self)
super().__init__()
self.faces_config = dict(
{"fill_opacity": 0.5, "shade_in_3d": True}, **faces_config
)

View file

@ -185,7 +185,7 @@ class BraceLabel(VMobject, metaclass=ConvertToOpenGL):
self.label = self.label_constructor(str(text), font_size=font_size)
self.brace.put_at_tip(self.label)
self.submobjects = [self.brace, self.label]
self.add(self.brace, self.label)
def creation_anim(self, label_anim=FadeIn, brace_anim=GrowFromCenter):
return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
@ -195,14 +195,12 @@ class BraceLabel(VMobject, metaclass=ConvertToOpenGL):
obj = self.get_group_class()(*obj)
self.brace = Brace(obj, self.brace_direction, **kwargs)
self.brace.put_at_tip(self.label)
self.submobjects[0] = self.brace
return self
def change_label(self, *text, **kwargs):
self.label = self.label_constructor(*text, **kwargs)
self.brace.put_at_tip(self.label)
self.submobjects[1] = self.label
return self
def change_brace_label(self, obj, *text, **kwargs):

View file

@ -1,42 +0,0 @@
from ...constants import *
from ..svg.svg_mobject import SVGMobject
from ..types.opengl_vectorized_mobject import OpenGLVMobject
from .opengl_svg_path import OpenGLSVGPathMobject
from .style_utils import cascade_element_style, parse_style
class OpenGLSVGMobject(SVGMobject):
def __init__(
self,
file_name=None,
should_center=True,
height=2,
width=None,
unpack_groups=True, # if False, creates a hierarchy of VGroups
stroke_width=DEFAULT_STROKE_WIDTH,
fill_opacity=1.0,
should_subdivide_sharp_curves=False,
should_remove_null_curves=False,
**kwargs,
):
self.def_map = {}
self.file_name = file_name or self.file_name
self.ensure_valid_file()
self.should_center = should_center
self.unpack_groups = unpack_groups
self.path_string_config = {
"should_subdivide_sharp_curves": should_subdivide_sharp_curves,
"should_remove_null_curves": should_remove_null_curves,
}
OpenGLVMobject.__init__(
self, stroke_width=stroke_width, fill_opacity=fill_opacity, **kwargs
)
self.move_into_position(width, height)
def init_points(self):
self.generate_points()
def path_string_to_mobject(self, path_string: str, style: dict):
return OpenGLSVGPathMobject(
path_string, **self.path_string_config, **parse_style(style)
)

View file

@ -1,29 +0,0 @@
import numpy as np
from ..types.opengl_vectorized_mobject import OpenGLVMobject
from .svg_path import SVGPathMobject
class OpenGLSVGPathMobject(SVGPathMobject):
def __init__(
self,
path_string,
should_subdivide_sharp_curves=False,
should_remove_null_curves=False,
**kwargs
):
self.path_string = path_string
OpenGLVMobject.__init__(
self,
long_lines=True,
should_subdivide_sharp_curves=should_subdivide_sharp_curves,
should_remove_null_curves=should_remove_null_curves,
**kwargs
)
self.current_path_start = np.zeros((1, self.dim))
def init_points(self):
self.generate_points()
def start_new_path(self, point):
SVGPathMobject.start_new_path(self, point)

View file

@ -2,6 +2,7 @@
__all__ = [
"ThreeDVMobject",
"Surface",
"ParametricSurface",
"Sphere",
"Dot3D",
@ -22,13 +23,14 @@ from colour import Color
from manim.mobject.opengl_compatibility import ConvertToOpenGL
from .. import config
from ..constants import *
from ..mobject.geometry import Circle, Square
from ..mobject.mobject import *
from ..mobject.opengl_mobject import OpenGLMobject
from ..mobject.types.vectorized_mobject import VGroup, VMobject
from ..utils.color import *
from ..utils.deprecation import deprecated_params
from ..utils.deprecation import deprecated, deprecated_params
from ..utils.iterables import tuplify
from ..utils.space_ops import normalize, z_to_vector
@ -38,8 +40,8 @@ class ThreeDVMobject(VMobject, metaclass=ConvertToOpenGL):
super().__init__(shade_in_3d=shade_in_3d, **kwargs)
class ParametricSurface(VGroup):
"""Creates a Parametric Surface
class Surface(VGroup, metaclass=ConvertToOpenGL):
"""Creates a Parametric Surface using a checkerboard pattern.
Parameters
----------
@ -65,7 +67,7 @@ class ParametricSurface(VGroup):
def construct(self):
axes = ThreeDAxes(x_range=[-4,4], x_length=8)
surface = ParametricSurface(
surface = Surface(
lambda u, v: axes.c2p(*self.func(u, v)),
u_range=[-PI, PI],
v_range=[0, TAU]
@ -188,7 +190,7 @@ class ParametricSurface(VGroup):
Returns
-------
:class:`~.ParametricSurface`
:class:`~.Surface`
The parametric surface with a gradient applied by value. For chaining.
Examples
@ -206,13 +208,12 @@ class ParametricSurface(VGroup):
y = v
z = np.sin(x) * np.cos(y)
return z
surface_plane = ParametricSurface(
surface_plane = Surface(
lambda u, v: axes.c2p(u, v, param_surface(u, v)),
resolution=(resolution_fa, resolution_fa),
v_min=0,
v_max=5,
u_min=0,
u_max=5)
v_range=[0, 5],
u_range=[0, 5],
)
surface_plane.set_style(fill_opacity=1)
surface_plane.set_fill_by_value(axes=axes, colors=[(RED, -0.4), (YELLOW, 0), (GREEN, 0.4)])
self.add(axes, surface_plane)
@ -245,16 +246,25 @@ class ParametricSurface(VGroup):
mob_color = interpolate_color(
new_colors[i - 1], new_colors[i], color_index
)
mob.set_color(mob_color, family=False)
if config.renderer == "opengl":
mob.set_color(mob_color, recurse=False)
else:
mob.set_color(mob_color, family=False)
break
return self
@deprecated(since="v0.10.0", replacement=Surface)
class ParametricSurface(Surface):
# shifts inheritance from Surface/OpenGLSurface depending on the renderer.
"""Creates a parametric surface"""
# Specific shapes
class Sphere(ParametricSurface):
class Sphere(Surface):
"""A mobject representing a three-dimensional sphere.
Examples
@ -287,27 +297,34 @@ class Sphere(ParametricSurface):
self,
center=ORIGIN,
radius=1,
resolution=(12, 24),
u_range=[0.001, PI - 0.001],
v_range=[0, TAU],
resolution=None,
u_range=(0, TAU),
v_range=(0, PI),
**kwargs
):
ParametricSurface.__init__(
self,
if config.renderer == "opengl":
res_value = (101, 51)
else:
res_value = (24, 12)
resolution = resolution if resolution is not None else res_value
self.radius = radius
super().__init__(
self.func,
resolution=resolution,
u_range=u_range,
v_range=v_range,
**kwargs,
)
self.radius = radius
self.scale(self.radius)
self.shift(center)
def func(
self, u, v
): # FIXME: An attribute defined in manim.mobject.three_dimensions line 56 hides this method
return np.array([np.cos(v) * np.sin(u), np.sin(v) * np.sin(u), np.cos(u)])
def func(self, u, v):
return self.radius * np.array(
[np.cos(u) * np.sin(v), np.sin(u) * np.sin(v), -np.cos(v)]
)
class Dot3D(Sphere):
@ -400,7 +417,7 @@ class Prism(Cube):
def __init__(self, dimensions=[3, 2, 1], **kwargs):
self.dimensions = dimensions
Cube.__init__(self, **kwargs)
super().__init__(**kwargs)
def generate_points(self):
Cube.generate_points(self)
@ -408,7 +425,7 @@ class Prism(Cube):
self.rescale_to_fit(value, dim, stretch=True)
class Cone(ParametricSurface):
class Cone(Surface):
"""A circular cone.
Can be defined using 2 parameters: its height, and its base radius.
The polar angle, theta, can be calculated using arctan(base_radius /
@ -459,8 +476,7 @@ class Cone(ParametricSurface):
self.direction = direction
self.theta = PI - np.arctan(base_radius / height)
ParametricSurface.__init__(
self,
super().__init__(
self.func,
v_range=v_range,
u_range=[u_min, np.sqrt(base_radius ** 2 + height ** 2)],
@ -540,7 +556,7 @@ class Cone(ParametricSurface):
return self.direction
class Cylinder(ParametricSurface):
class Cylinder(Surface):
"""A cylinder, defined by its height, radius and direction,
Examples
@ -576,13 +592,12 @@ class Cylinder(ParametricSurface):
direction=Z_AXIS,
v_range=[0, TAU],
show_ends=True,
resolution=24,
resolution=(24, 24),
**kwargs
):
self._height = height
self.radius = radius
ParametricSurface.__init__(
self,
super().__init__(
self.func,
resolution=resolution,
u_range=[-self._height / 2, self._height / 2],
@ -715,8 +730,7 @@ class Line3D(Cylinder):
# start and end, if they're mobjects
self.start = self.pointify(start, self.direction)
self.end = self.pointify(end, -self.direction)
Cylinder.__init__(
self,
super().__init__(
height=np.linalg.norm(self.vect),
radius=self.thickness,
direction=self.direction,
@ -779,8 +793,8 @@ class Arrow3D(Line3D):
color=WHITE,
**kwargs
):
Line3D.__init__(
self, start=start, end=end, thickness=thickness, color=color, **kwargs
super().__init__(
start=start, end=end, thickness=thickness, color=color, **kwargs
)
self.length = np.linalg.norm(self.vect)
@ -798,7 +812,7 @@ class Arrow3D(Line3D):
self.set_color(color)
class Torus(ParametricSurface):
class Torus(Surface):
"""A torus.
Examples
@ -825,15 +839,21 @@ class Torus(ParametricSurface):
self,
major_radius=3,
minor_radius=1,
u_range=[0, TAU],
v_range=[0, TAU],
resolution=24,
u_range=(0, TAU),
v_range=(0, TAU),
resolution=None,
**kwargs
):
if config.renderer == "opengl":
res_value = (101, 101)
else:
res_value = (24, 24)
resolution = resolution if resolution is not None else res_value
self.R = major_radius
self.r = minor_radius
ParametricSurface.__init__(
self,
super().__init__(
self.func,
u_range=u_range,
v_range=v_range,

View file

@ -204,6 +204,83 @@ class OpenGLSurface(OpenGLMobject):
def get_shader_vert_indices(self):
return self.get_triangle_indices()
def set_fill_by_value(self, axes, colors):
# directly copied from three_dimensions.py with some compatibility changes.
"""Sets the color of each mobject of a parametric surface to a color relative to its z-value
Parameters
----------
axes :
The axes for the parametric surface, which will be used to map z-values to colors.
colors :
A list of colors, ordered from lower z-values to higher z-values. If a list of tuples is passed
containing colors paired with numbers, then those numbers will be used as the pivots.
Returns
-------
:class:`~.Surface`
The parametric surface with a gradient applied by value. For chaining.
Examples
--------
.. manim:: FillByValueExample
:save_last_frame:
class FillByValueExample(ThreeDScene):
def construct(self):
resolution_fa = 42
self.set_camera_orientation(phi=75 * DEGREES, theta=-120 * DEGREES)
axes = ThreeDAxes(x_range=(0, 5, 1), y_range=(0, 5, 1), z_range=(-1, 1, 0.5))
def param_surface(u, v):
x = u
y = v
z = np.sin(x) * np.cos(y)
return z
surface_plane = Surface(
lambda u, v: axes.c2p(u, v, param_surface(u, v)),
resolution=(resolution_fa, resolution_fa),
v_range=[0, 5],
u_range=[0, 5],
)
# surface_plane.set_style(fill_opacity=1)
surface_plane.set_fill_by_value(axes=axes, colors=[(RED, -0.4), (YELLOW, 0), (GREEN, 0.4)])
self.add(axes, surface_plane)
"""
if type(colors[0]) is tuple:
new_colors, pivots = [[i for i, j in colors], [j for i, j in colors]]
else:
new_colors = colors
pivot_min = axes.z_range[0]
pivot_max = axes.z_range[1]
pivot_frequency = (pivot_max - pivot_min) / (len(new_colors) - 1)
pivots = np.arange(
start=pivot_min, stop=pivot_max + pivot_frequency, step=pivot_frequency
)
for mob in self.family_members_with_points():
# import ipdb; ipdb.set_trace(context=7)
z_value = axes.point_to_coords(mob.get_midpoint())[2]
if z_value <= pivots[0]:
mob.set_color(new_colors[0])
elif z_value >= pivots[-1]:
mob.set_color(new_colors[-1])
else:
for i, pivot in enumerate(pivots):
if pivot > z_value:
color_index = (z_value - pivots[i - 1]) / (
pivots[i] - pivots[i - 1]
)
color_index = min(color_index, 1)
mob_color = interpolate_color(
new_colors[i - 1], new_colors[i], color_index
)
mob.set_color(mob_color, recurse=False)
break
return self
class OpenGLSurfaceGroup(OpenGLSurface):
def __init__(self, *parametric_surfaces, resolution=None, **kwargs):

View file

@ -3,12 +3,8 @@ try:
except ImportError:
pass
from ..mobject.opengl_geometry import *
from ..mobject.opengl_mobject import *
from ..mobject.opengl_three_dimensions import *
from ..mobject.svg.opengl_svg_mobject import *
from ..mobject.svg.opengl_tex_mobject import *
from ..mobject.svg.opengl_text_mobject import *
from ..mobject.types.opengl_surface import *
from ..mobject.types.opengl_vectorized_mobject import *
from ..renderer.shader import *

View file

@ -406,11 +406,11 @@ class SpecialThreeDScene(ThreeDScene):
Parameters
----------
**kwargs
Any valid parameter of :class:`.Sphere` or :class:`.ParametricSurface`.
Any valid parameter of :class:`~.Sphere` or :class:`~.Surface`.
Returns
-------
:class:`.Sphere`
:class:`~.Sphere`
The sphere object.
"""
config = merge_dicts_recursively(self.sphere_config, kwargs)

View file

@ -1,7 +1,6 @@
import pytest
from manim import *
from manim.opengl import *
from manim.renderer.opengl_renderer import OpenGLRenderer
from tests.test_graphical_units.testing.frames_comparison import frames_comparison
@ -10,6 +9,6 @@ __module_test__ = "opengl"
@frames_comparison(renderer_class=OpenGLRenderer, renderer="opengl")
def test_Circle(scene):
circle = OpenGLCircle().set_color(RED)
circle = Circle().set_color(RED)
scene.add(circle)
scene.wait()

View file

@ -114,7 +114,7 @@ def test_SurfaceColorscale(scene):
z = y ** 2 / 2 - x ** 2 / 2
return z
trig_plane = ParametricSurface(
trig_plane = Surface(
lambda x, y: axes.c2p(x, y, param_trig(x, y)),
resolution=(resolution_fa, resolution_fa),
v_min=-3,

View file

@ -1,12 +1,14 @@
import numpy as np
import pytest
from manim import config
from manim.constants import RIGHT
from manim.opengl import OpenGLSquare
from manim.mobject.geometry import Square
def test_Data():
a = OpenGLSquare().move_to(RIGHT)
config.renderer = "opengl"
a = Square().move_to(RIGHT)
data_bb = a.data["bounding_box"]
assert np.array_equal(
data_bb, np.array([[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [2.0, 1.0, 0.0]])
@ -34,3 +36,4 @@ def test_Data():
)
assert np.array_equal(a.bounding_box, data_bb)
config.renderer = "cairo" # needs to be here or else the following cairo tests fail