def animated_camera(cfg, rotate=True): '''Animated camera around a scene''' g = ngl.Group() q = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) m = ngl.Media(cfg.medias[0].filename) t = ngl.Texture2D(data_src=m) program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) program.update_vert_out_vars(var_uvcoord=ngl.IOVec2(), var_tex0_coord=ngl.IOVec2()) node = ngl.Render(q, program) node.update_frag_resources(tex0=t) g.add_children(node) translate = ngl.Translate(node, vector=(-0.6, 0.8, -1)) g.add_children(translate) translate = ngl.Translate(node, vector=(0.6, 0.8, -1)) g.add_children(translate) translate = ngl.Translate(node, vector=(-0.6, -0.5, -1)) g.add_children(translate) translate = ngl.Translate(node, vector=(0.6, -0.5, -1)) g.add_children(translate) g = ngl.GraphicConfig(g, depth_test=True) camera = ngl.Camera(g) camera.set_eye(0, 0, 2) camera.set_center(0.0, 0.0, 0.0) camera.set_up(0.0, 1.0, 0.0) camera.set_perspective(45.0, cfg.aspect_ratio_float) camera.set_clipping(0.1, 10.0) tr_animkf = [ ngl.AnimKeyFrameVec3(0, (0.0, 0.0, 0.0)), ngl.AnimKeyFrameVec3(10, (0.0, 0.0, 3.0), 'exp_out') ] node = ngl.Translate(ngl.Identity(), anim=ngl.AnimatedVec3(tr_animkf)) if rotate: rot_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360, 'exp_out') ] node = ngl.Rotate(node, axis=(0, 1, 0), anim=ngl.AnimatedFloat(rot_animkf)) camera.set_eye_transform(node) fov_animkf = [ ngl.AnimKeyFrameFloat(0.5, 60.0), ngl.AnimKeyFrameFloat(cfg.duration, 45.0, 'exp_out') ] camera.set_fov_anim(ngl.AnimatedFloat(fov_animkf)) return camera
def simple_transition(cfg, transition_start=2, transition_duration=4): '''Fading transition between two medias''' cfg.duration = transition_start * 2 + transition_duration vertex = cfg.get_vert('dual-tex') fragment = cfg.get_frag('tex-mix') q = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) p = ngl.Program() p1_2 = ngl.Program(vertex=vertex, fragment=fragment) m1 = ngl.Media(cfg.medias[0].filename, label='media #1') m2 = ngl.Media(cfg.medias[1 % len(cfg.medias)].filename, label='media #2') animkf_m2 = [ngl.AnimKeyFrameFloat(transition_start, 0)] m2.set_time_anim(ngl.AnimatedFloat(animkf_m2)) t1 = ngl.Texture2D(data_src=m1, label='texture #1') t2 = ngl.Texture2D(data_src=m2, label='texture #2') render1 = ngl.Render(q, p, label='render #1') render1.update_textures(tex0=t1) render2 = ngl.Render(q, p, label='render #2') render2.update_textures(tex0=t2) delta_animkf = [ ngl.AnimKeyFrameFloat(transition_start, 1.0), ngl.AnimKeyFrameFloat(transition_start + transition_duration, 0.0) ] delta = ngl.UniformFloat(value=1.0, anim=ngl.AnimatedFloat(delta_animkf)) render1_2 = ngl.Render(q, p1_2, label='transition') render1_2.update_textures(tex0=t1, tex1=t2) render1_2.update_uniforms(delta=delta) rr1 = [] rr2 = [] rr1_2 = [] rr1.append(ngl.TimeRangeModeNoop(transition_start)) rr2.append(ngl.TimeRangeModeNoop(0)) rr2.append(ngl.TimeRangeModeCont(transition_start + transition_duration)) rr1_2.append(ngl.TimeRangeModeNoop(0)) rr1_2.append(ngl.TimeRangeModeCont(transition_start)) rr1_2.append(ngl.TimeRangeModeNoop(transition_start + transition_duration)) rf1 = ngl.TimeRangeFilter(render1, ranges=rr1) rf2 = ngl.TimeRangeFilter(render2, ranges=rr2) rf1_2 = ngl.TimeRangeFilter(render1_2, ranges=rr1_2) g = ngl.Group() g.add_children(rf1, rf1_2, rf2) return g
def transform_animated_camera(cfg): cfg.duration = 5. g = ngl.Group() elems = ( ('red', None), ('yellow', (-0.6, 0.8, -1)), ('green', (0.6, 0.8, -1)), ('cyan', (-0.6, -0.5, -1)), ('magenta', (0.6, -0.5, -1)), ) quad = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) prog = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) for color, vector in elems: node = ngl.Render(quad, prog) node.update_uniforms(color=ngl.UniformVec4(value=COLORS[color])) if vector: node = ngl.Translate(node, vector=vector) g.add_children(node) g = ngl.GraphicConfig(g, depth_test=True) camera = ngl.Camera(g) camera.set_eye(0, 0, 2) camera.set_center(0.0, 0.0, 0.0) camera.set_up(0.0, 1.0, 0.0) camera.set_perspective(45.0, cfg.aspect_ratio_float) camera.set_clipping(0.1, 10.0) tr_animkf = [ ngl.AnimKeyFrameVec3(0, (0.0, 0.0, 0.0)), ngl.AnimKeyFrameVec3(cfg.duration, (0.0, 0.0, 3.0)) ] eye_transform = ngl.Translate(ngl.Identity(), anim=ngl.AnimatedVec3(tr_animkf)) rot_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360) ] eye_transform = ngl.Rotate(eye_transform, axis=(0, 1, 0), anim=ngl.AnimatedFloat(rot_animkf)) camera.set_eye_transform(eye_transform) fov_animkf = [ ngl.AnimKeyFrameFloat(0.5, 60.0), ngl.AnimKeyFrameFloat(cfg.duration, 45.0) ] camera.set_fov_anim(ngl.AnimatedFloat(fov_animkf)) return camera
def queued_medias(cfg, overlap_time=1., dim=3): '''Queue of medias, mainly used as a demonstration for the prefetch/release mechanism''' qw = qh = 2. / dim nb_videos = dim * dim tqs = [] p = ngl.Program() for y in range(dim): for x in range(dim): video_id = y * dim + x start = video_id * cfg.duration / nb_videos animkf = [ngl.AnimKeyFrameFloat(start, 0)] m = ngl.Media(cfg.medias[video_id % len(cfg.medias)].filename, time_anim=ngl.AnimatedFloat(animkf)) m.set_label('media #%d' % video_id) corner = (-1. + x * qw, 1. - (y + 1) * qh, 0) q = ngl.Quad(corner, (qw, 0, 0), (0, qh, 0)) t = ngl.Texture2D(data_src=m) render = ngl.Render(q, p) render.set_label('render #%d' % video_id) render.update_textures(tex0=t) rf = ngl.TimeRangeFilter(render) if start: rf.add_ranges(ngl.TimeRangeModeNoop(0)) rf.add_ranges( ngl.TimeRangeModeCont(start), ngl.TimeRangeModeNoop(start + cfg.duration / nb_videos + overlap_time)) tqs.append(rf) return ngl.Group(children=tqs)
def triangle(cfg, size=0.5): '''Rotating triangle with edge coloring specified in a vertex attribute''' b = size * math.sqrt(3) / 2.0 c = size * 1 / 2. cfg.duration = 3. cfg.aspect_ratio = (1, 1) colors_data = array.array( 'f', [0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0]) colors_buffer = ngl.BufferVec4(data=colors_data) triangle = ngl.Triangle((-b, -c, 0), (b, -c, 0), (0, size, 0)) p = ngl.Program(fragment=cfg.get_frag('color'), vertex=cfg.get_vert('triangle')) p.update_vert_out_vars(color=ngl.IOVec4()) node = ngl.Render(triangle, p) node.update_attributes(edge_color=colors_buffer) animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration / 3., -360 / 3., 'exp_in_out'), ngl.AnimKeyFrameFloat(2 * cfg.duration / 3., -2 * 360 / 3., 'exp_in_out'), ngl.AnimKeyFrameFloat(cfg.duration, -360, 'exp_in_out') ] node = ngl.Rotate(node, anim=ngl.AnimatedFloat(animkf)) return node
def velocity_triangle_rotate(cfg): cfg.duration = 5.0 cfg.aspect_ratio = (1, 1) anim_kf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360 * 3, "circular_in_out"), ] anim = ngl.AnimatedFloat(anim_kf) velocity = ngl.VelocityFloat(anim) frag = textwrap.dedent("""\ void main() { float v = clamp(velocity / 3000., 0.0, 1.0); ngl_out_color = vec4(v, v / 2.0, 0.0, 1.0); } """) p0, p1, p2 = equilateral_triangle_coords(2.0) triangle = ngl.RenderColor(COLORS.white, geometry=ngl.Triangle(p0, p1, p2)) triangle = ngl.Rotate(triangle, angle=anim) prog_c = ngl.Program(vertex=cfg.get_vert("color"), fragment=frag) circle = ngl.Render(ngl.Circle(radius=1.0, npoints=128), prog_c) circle.update_frag_resources(velocity=velocity) return ngl.Group(children=(circle, triangle))
def animated_square(cfg, color=(1, 0.66, 0), rotate=True, scale=True, translate=True): """Animated Translate/Scale/Rotate on a square""" cfg.duration = 5.0 cfg.aspect_ratio = (1, 1) sz = 1 / 3.0 q = ngl.Quad((-sz / 2, -sz / 2, 0), (sz, 0, 0), (0, sz, 0)) node = ngl.RenderColor(color, geometry=q) coords = [(-1, 1), (1, 1), (1, -1), (-1, -1), (-1, 1)] if rotate: animkf = (ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360)) node = ngl.Rotate(node, angle=ngl.AnimatedFloat(animkf)) if scale: animkf = ( ngl.AnimKeyFrameVec3(0, (1, 1, 1)), ngl.AnimKeyFrameVec3(cfg.duration / 2, (2, 2, 2)), ngl.AnimKeyFrameVec3(cfg.duration, (1, 1, 1)), ) node = ngl.Scale(node, factors=ngl.AnimatedVec3(animkf)) if translate: animkf = [] tscale = 1.0 / float(len(coords) - 1) * cfg.duration for i, xy in enumerate(coords): pos = (xy[0] * 0.5, xy[1] * 0.5, 0) t = i * tscale animkf.append(ngl.AnimKeyFrameVec3(t, pos)) node = ngl.Translate(node, vector=ngl.AnimatedVec3(animkf)) return node
def animated_uniform(cfg): """Uniform mat4 animated with a transform chain""" m0 = cfg.medias[0] cfg.aspect_ratio = (m0.width, m0.height) q = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) m = ngl.Media(m0.filename) t = ngl.Texture2D(data_src=m) p = ngl.Program(vertex=cfg.get_vert("texture"), fragment=cfg.get_frag("matrix-transform")) p.update_vert_out_vars(var_uvcoord=ngl.IOVec2(), var_tex0_coord=ngl.IOVec2()) ts = ngl.Render(q, p) ts.update_frag_resources(tex0=t) scale_animkf = [ ngl.AnimKeyFrameVec3(0, (1, 1, 1)), ngl.AnimKeyFrameVec3(cfg.duration, (0.1, 0.1, 0.1), "quartic_out"), ] s = ngl.Scale(ngl.Identity(), factors=ngl.AnimatedVec3(scale_animkf)) rotate_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360, "exp_out") ] r = ngl.Rotate(s, axis=(0, 0, 1), angle=ngl.AnimatedFloat(rotate_animkf)) u = ngl.UniformMat4(transform=r) ts.update_frag_resources(matrix=u) return ts
def _get_rtt_scene(cfg, features='depth', texture_ds_format=None, samples=0, mipmap_filter='none', sample_depth=False): cfg.duration = 10 cfg.aspect_ratio = (1, 1) cube = _get_cube() program = ngl.Program(vertex=_RENDER_CUBE_VERT, fragment=_RENDER_CUBE_FRAG) program.update_vert_out_vars(var_normal=ngl.IOVec3()) render = ngl.Render(cube, program) render = ngl.Scale(render, (0.5, 0.5, 0.5)) for i in range(3): rot_animkf = ngl.AnimatedFloat([ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360 * (i + 1)) ]) axis = [int(i == x) for x in range(3)] render = ngl.Rotate(render, axis=axis, anim=rot_animkf) config = ngl.GraphicConfig(render, depth_test=True) camera = ngl.Camera( config, eye=(0.0, 0.0, 3.0), center=(0.0, 0.0, 0.0), up=(0.0, 1.0, 0.0), perspective=(45.0, cfg.aspect_ratio_float), clipping=(1.0, 10.0), ) size = 1024 texture_depth = None if texture_ds_format: texture_depth = ngl.Texture2D(width=size, height=size, format=texture_ds_format) texture = ngl.Texture2D( width=size, height=size, min_filter='linear', mipmap_filter=mipmap_filter, ) rtt = ngl.RenderToTexture( camera, [texture], features=features, depth_texture=texture_depth, samples=samples, clear_color=(0, 0, 0, 1), ) quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) if sample_depth: program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=_RENDER_DEPTH) program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) render = ngl.Render(quad, program) render.update_frag_resources(tex0=texture_depth) else: program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) render = ngl.Render(quad, program) render.update_frag_resources(tex0=texture) return ngl.Group(children=(rtt, render))
def _compute_animation(cfg, animate_pre_render=True): cfg.duration = 5 cfg.aspect_ratio = (1, 1) local_size = 2 vertices_data = array.array( "f", [ # fmt: off -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, -0.5, 0.5, 0.0, 0.5, 0.5, 0.0, # fmt: on ], ) nb_vertices = 4 input_vertices = ngl.BufferVec3(data=vertices_data, label="vertices") output_vertices = ngl.BufferVec3(data=vertices_data, label="vertices") input_block = ngl.Block(fields=[input_vertices], layout="std140") output_block = ngl.Block(fields=[output_vertices], layout="std140") rotate_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360) ] rotate = ngl.Rotate(ngl.Identity(), axis=(0, 0, 1), angle=ngl.AnimatedFloat(rotate_animkf)) transform = ngl.UniformMat4(transform=rotate) program = ngl.ComputeProgram(_ANIMATION_COMPUTE, workgroup_size=(local_size, local_size, 1)) program.update_properties(dst=ngl.ResourceProps(writable=True)) compute = ngl.Compute(workgroup_count=(nb_vertices / (local_size**2), 1, 1), program=program) compute.update_resources(transform=transform, src=input_block, dst=output_block) quad_buffer = ngl.BufferVec3(block=output_block, block_field=0) geometry = ngl.Geometry(quad_buffer, topology="triangle_strip") program = ngl.Program(vertex=cfg.get_vert("color"), fragment=cfg.get_frag("color")) render = ngl.Render(geometry, program) render.update_frag_resources(color=ngl.UniformVec3(value=COLORS.sgreen), opacity=ngl.UniformFloat(1)) children = (compute, render) if animate_pre_render else (render, compute) return ngl.Group(children=children)
def triangle(cfg, size=4 / 3): """Rotating triangle with edge coloring specified in a vertex attribute""" cfg.duration = 3.0 cfg.aspect_ratio = (1, 1) colors_data = array.array("f", [0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0]) colors_buffer = ngl.BufferVec3(data=colors_data) p0, p1, p2 = equilateral_triangle_coords(size) triangle = ngl.Triangle(p0, p1, p2) p = ngl.Program(fragment=cfg.get_frag("color"), vertex=cfg.get_vert("triangle")) p.update_vert_out_vars(color=ngl.IOVec3()) node = ngl.Render(triangle, p) node.update_attributes(edge_color=colors_buffer) node.update_frag_resources(opacity=ngl.UniformFloat(1)) animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration / 3.0, -360 / 3.0, "exp_in_out"), ngl.AnimKeyFrameFloat(2 * cfg.duration / 3.0, -2 * 360 / 3.0, "exp_in_out"), ngl.AnimKeyFrameFloat(cfg.duration, -360, "exp_in_out"), ] node = ngl.Rotate(node, angle=ngl.AnimatedFloat(animkf)) return node
def compute_particules(cfg): random.seed(0) cfg.duration = 10 local_size = 4 nb_particules = 128 shader_version = '310 es' if cfg.backend == 'gles' else '430' shader_data = dict( version=shader_version, local_size=local_size, nb_particules=nb_particules, ) compute_shader = _PARTICULES_COMPUTE % shader_data vertex_shader = _PARTICULES_VERT % shader_data fragment_shader = _PARTICULES_FRAG % shader_data positions = array.array('f') velocities = array.array('f') for i in range(nb_particules): positions.extend([ random.uniform(-2.0, 1.0), random.uniform(-1.0, 1.0), 0.0, ]) velocities.extend([ random.uniform(1.0, 2.0), random.uniform(0.5, 1.5), ]) ipositions = ngl.Block( fields=[ ngl.BufferVec3(data=positions), ngl.BufferVec2(data=velocities), ], layout='std430', ) opositions = ngl.Block(fields=[ngl.BufferVec3(count=nb_particules)], layout='std430') animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 1.0), ] time = ngl.AnimatedFloat(animkf) duration = ngl.UniformFloat(cfg.duration) group_size = nb_particules / local_size program = ngl.ComputeProgram(compute_shader) compute = ngl.Compute(nb_particules, 1, 1, program) compute.update_uniforms(time=time, duration=duration) compute.update_blocks(ipositions_buffer=ipositions, opositions_buffer=opositions) circle = ngl.Circle(radius=0.05) program = ngl.Program(vertex=vertex_shader, fragment=fragment_shader) render = ngl.Render(circle, program, nb_instances=nb_particules) render.update_uniforms(color=ngl.UniformVec4(value=COLORS['sgreen'])) render.update_blocks(positions_buffer=opositions) group = ngl.Group() group.add_children(compute, render) return group
def animated_uniform(cfg): '''Uniform mat4 animated with a transform chain''' m0 = cfg.medias[0] cfg.aspect_ratio = (m0.width, m0.height) q = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) m = ngl.Media(m0.filename) t = ngl.Texture2D(data_src=m) p = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('matrix-transform')) ts = ngl.Render(q, p) ts.update_textures(tex0=t) scale_animkf = [ ngl.AnimKeyFrameVec3(0, (1, 1, 1)), ngl.AnimKeyFrameVec3(cfg.duration, (0.1, 0.1, 0.1), 'quartic_out') ] s = ngl.Scale(ngl.Identity(), anim=ngl.AnimatedVec3(scale_animkf)) rotate_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360, 'exp_out') ] r = ngl.Rotate(s, axis=(0, 0, 1), anim=ngl.AnimatedFloat(rotate_animkf)) u = ngl.UniformMat4(transform=r) ts.update_uniforms(matrix=u) return ts
def rtt_clear_attachment_with_timeranges(cfg): cfg.aspect_ratio = (1, 1) # Time-disabled full screen white quad render = ngl.RenderColor(COLORS.white) time_range_filter = ngl.TimeRangeFilter(render) time_range_filter.add_ranges(ngl.TimeRangeModeNoop(0)) # Intermediate no-op RTT to force the use of a different render pass internally texture = ngl.Texture2D(width=32, height=32) rtt_noop = ngl.RenderToTexture(ngl.Identity(), [texture]) # Centered rotating quad quad = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) render = ngl.RenderColor(COLORS.orange, geometry=quad) animkf = [ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, -360)] render = ngl.Rotate(render, angle=ngl.AnimatedFloat(animkf)) group = ngl.Group(children=(time_range_filter, rtt_noop, render)) # Root RTT texture = ngl.Texture2D(width=512, height=512) rtt = ngl.RenderToTexture(group, [texture]) # Full screen render of the root RTT result render = ngl.RenderTexture(texture) return ngl.Group(children=(rtt, render))
def simple_transition(cfg, transition_start=2, transition_duration=4): """Fading transition between two medias""" cfg.duration = transition_start * 2 + transition_duration vertex = cfg.get_vert("dual-tex") fragment = cfg.get_frag("tex-mix") q = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) p1_2 = ngl.Program(vertex=vertex, fragment=fragment) p1_2.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_tex1_coord=ngl.IOVec2()) m1 = ngl.Media(cfg.medias[0].filename, label="media #1") m2 = ngl.Media(cfg.medias[1 % len(cfg.medias)].filename, label="media #2") animkf_m2 = [ ngl.AnimKeyFrameFloat(transition_start, 0), ngl.AnimKeyFrameFloat(transition_start + cfg.duration, cfg.duration), ] m2.set_time_anim(ngl.AnimatedTime(animkf_m2)) t1 = ngl.Texture2D(data_src=m1, label="texture #1") t2 = ngl.Texture2D(data_src=m2, label="texture #2") render1 = ngl.RenderTexture(t1, label="render #1") render2 = ngl.RenderTexture(t2, label="render #2") delta_animkf = [ ngl.AnimKeyFrameFloat(transition_start, 1.0), ngl.AnimKeyFrameFloat(transition_start + transition_duration, 0.0), ] delta = ngl.AnimatedFloat(delta_animkf) render1_2 = ngl.Render(q, p1_2, label="transition") render1_2.update_frag_resources(tex0=t1, tex1=t2) render1_2.update_frag_resources(delta=delta) rr1 = [] rr2 = [] rr1_2 = [] rr1.append(ngl.TimeRangeModeNoop(transition_start)) rr2.append(ngl.TimeRangeModeNoop(0)) rr2.append(ngl.TimeRangeModeCont(transition_start + transition_duration)) rr1_2.append(ngl.TimeRangeModeNoop(0)) rr1_2.append(ngl.TimeRangeModeCont(transition_start)) rr1_2.append(ngl.TimeRangeModeNoop(transition_start + transition_duration)) rf1 = ngl.TimeRangeFilter(render1, ranges=rr1) rf2 = ngl.TimeRangeFilter(render2, ranges=rr2) rf1_2 = ngl.TimeRangeFilter(render1_2, ranges=rr1_2) g = ngl.Group() g.add_children(rf1, rf1_2, rf2) return g
def cube(cfg, display_depth_buffer=False): ''' Cube with a common media Texture but a different color tainting on each side. Also includes a depth map visualization. ''' cube = ngl.Group(label='cube') frag_data = cfg.get_frag('tex-tint') program = ngl.Program(fragment=frag_data) texture = ngl.Texture2D(data_src=ngl.Media(cfg.medias[0].filename)) children = [_get_cube_side(texture, program, qi[0], qi[1], qi[2], qi[3]) for qi in _get_cube_quads()] cube.add_children(*children) for i in range(3): rot_animkf = ngl.AnimatedFloat([ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360 * (i + 1))]) axis = [int(i == x) for x in range(3)] cube = ngl.Rotate(cube, axis=axis, anim=rot_animkf) config = ngl.GraphicConfig(cube, depth_test=True) camera = ngl.Camera(config) camera.set_eye(0.0, 0.0, 2.0) camera.set_center(0.0, 0.0, 0.0) camera.set_up(0.0, 1.0, 0.0) camera.set_perspective(45.0, cfg.aspect_ratio_float) camera.set_clipping(1.0, 10.0) if not display_depth_buffer: return camera else: group = ngl.Group() depth_texture = ngl.Texture2D() depth_texture.set_format('d16_unorm') depth_texture.set_width(640) depth_texture.set_height(480) texture = ngl.Texture2D() texture.set_width(640) texture.set_height(480) rtt = ngl.RenderToTexture(camera) rtt.add_color_textures(texture) rtt.set_depth_texture(depth_texture) quad = ngl.Quad((-1.0, -1.0, 0), (1, 0, 0), (0, 1, 0)) program = ngl.Program() render = ngl.Render(quad, program) render.update_textures(tex0=texture) group.add_children(rtt, render) quad = ngl.Quad((0.0, 0.0, 0), (1, 0, 0), (0, 1, 0)) program = ngl.Program() render = ngl.Render(quad, program) render.update_textures(tex0=depth_texture) group.add_children(rtt, render) return group
def compute_particles(cfg): cfg.duration = 10 workgroups = (2, 1, 4) local_size = (4, 4, 1) nb_particles = workgroups[0] * workgroups[1] * workgroups[2] * local_size[ 0] * local_size[1] * local_size[2] positions = array.array("f") velocities = array.array("f") for i in range(nb_particles): positions.extend([ cfg.rng.uniform(-2.0, 1.0), cfg.rng.uniform(-1.0, 1.0), 0.0, ]) velocities.extend([ cfg.rng.uniform(1.0, 2.0), cfg.rng.uniform(0.5, 1.5), ]) ipositions = ngl.Block( fields=[ ngl.BufferVec3(data=positions, label="positions"), ngl.BufferVec2(data=velocities, label="velocities"), ], layout="std430", ) opositions = ngl.Block( fields=[ngl.BufferVec3(count=nb_particles, label="positions")], layout="std140") animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 1.0), ] time = ngl.AnimatedFloat(animkf) duration = ngl.UniformFloat(cfg.duration) program = ngl.ComputeProgram(_PARTICULES_COMPUTE, workgroup_size=local_size) program.update_properties(odata=ngl.ResourceProps(writable=True)) compute = ngl.Compute(workgroups, program) compute.update_resources(time=time, duration=duration, idata=ipositions, odata=opositions) circle = ngl.Circle(radius=0.05) program = ngl.Program(vertex=_PARTICULES_VERT, fragment=cfg.get_frag("color")) render = ngl.Render(circle, program, nb_instances=nb_particles) render.update_frag_resources(color=ngl.UniformVec3(value=COLORS.sgreen), opacity=ngl.UniformFloat(1)) render.update_vert_resources(data=opositions) group = ngl.Group() group.add_children(compute, render) return group
def compute_particules(cfg): random.seed(0) cfg.duration = 10 local_size = 4 nb_particules = 128 positions = array.array('f') velocities = array.array('f') for i in range(nb_particules): positions.extend([ random.uniform(-2.0, 1.0), random.uniform(-1.0, 1.0), 0.0, ]) velocities.extend([ random.uniform(1.0, 2.0), random.uniform(0.5, 1.5), ]) ipositions = ngl.Block( fields=[ ngl.BufferVec3(data=positions, label='positions'), ngl.BufferVec2(data=velocities, label='velocities'), ], layout='std430', ) opositions = ngl.Block( fields=[ngl.BufferVec3(count=nb_particules, label='positions')], layout='std430') animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 1.0), ] time = ngl.AnimatedFloat(animkf) duration = ngl.UniformFloat(cfg.duration) group_size = nb_particules / local_size program = ngl.ComputeProgram(_PARTICULES_COMPUTE % dict(local_size=local_size)) compute = ngl.Compute(nb_particules, 1, 1, program) compute.update_resources(time=time, duration=duration, idata=ipositions, odata=opositions) circle = ngl.Circle(radius=0.05) program = ngl.Program(vertex=_PARTICULES_VERT, fragment=cfg.get_frag('color')) render = ngl.Render(circle, program, nb_instances=nb_particules) render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['sgreen'])) render.update_vert_resources(data=opositions) group = ngl.Group() group.add_children(compute, render) return group
def stl(cfg, stl=None, scale=0.8): """Load and display a sphere generated with OpenSCAD""" if stl is None: # generated with: echo 'sphere($fn=15);'>sphere.scad; openscad sphere.scad -o sphere.stl stl = op.join(op.dirname(__file__), "data", "sphere.stl") normals_data = array.array("f") vertices_data = array.array("f") solid_label = None normal = None with open(stl) as fp: for line in fp.readlines(): line = line.strip() if line.startswith("solid"): solid_label = line.split(None, 1)[1] elif line.startswith("facet normal"): _, _, normal = line.split(None, 2) normal = [float(f) for f in normal.split()] elif normal and line.startswith("vertex"): _, vertex = line.split(None, 1) vertex = [float(f) for f in vertex.split()] normals_data.extend(normal) vertices_data.extend(vertex) vertices = ngl.BufferVec3(data=vertices_data) normals = ngl.BufferVec3(data=normals_data) g = ngl.Geometry(vertices=vertices, normals=normals) p = ngl.Program(vertex=cfg.get_vert("colored-normals"), fragment=cfg.get_frag("colored-normals")) p.update_vert_out_vars(var_normal=ngl.IOVec3(), var_uvcoord=ngl.IOVec2(), var_tex0_coord=ngl.IOVec2()) solid = ngl.Render(g, p, label=solid_label) solid = ngl.GraphicConfig(solid, depth_test=True) solid = ngl.Scale(solid, [scale] * 3) for i in range(3): rot_animkf = ngl.AnimatedFloat([ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360 * (i + 1)) ]) axis = [int(i == x) for x in range(3)] solid = ngl.Rotate(solid, axis=axis, angle=rot_animkf) camera = ngl.Camera(solid) camera.set_eye(2.0, 2.0, 2.0) camera.set_center(0.0, 0.0, 0.0) camera.set_up(0.0, 1.0, 0.0) camera.set_perspective(45.0, cfg.aspect_ratio_float) camera.set_clipping(1.0, 10.0) return camera
def animated_camera(cfg, rotate=True): """Animated camera around a scene""" g = ngl.Group() q = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) m = ngl.Media(cfg.medias[0].filename) t = ngl.Texture2D(data_src=m) node = ngl.RenderTexture(t, geometry=q) g.add_children(node) translate = ngl.Translate(node, vector=(-0.6, 0.8, -1)) g.add_children(translate) translate = ngl.Translate(node, vector=(0.6, 0.8, -1)) g.add_children(translate) translate = ngl.Translate(node, vector=(-0.6, -0.5, -1)) g.add_children(translate) translate = ngl.Translate(node, vector=(0.6, -0.5, -1)) g.add_children(translate) g = ngl.GraphicConfig(g, depth_test=True) camera = ngl.Camera(g) camera.set_eye(0, 0, 2) camera.set_center(0.0, 0.0, 0.0) camera.set_up(0.0, 1.0, 0.0) camera.set_clipping(0.1, 10.0) tr_animkf = [ ngl.AnimKeyFrameVec3(0, (0.0, 0.0, 0.0)), ngl.AnimKeyFrameVec3(10, (0.0, 0.0, 3.0), "exp_out") ] node = ngl.Translate(ngl.Identity(), vector=ngl.AnimatedVec3(tr_animkf)) if rotate: rot_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360, "exp_out") ] node = ngl.Rotate(node, axis=(0, 1, 0), angle=ngl.AnimatedFloat(rot_animkf)) camera.set_eye_transform(node) perspective_animkf = [ ngl.AnimKeyFrameVec2(0.5, (60.0, cfg.aspect_ratio_float)), ngl.AnimKeyFrameVec2(cfg.duration, (45.0, cfg.aspect_ratio_float), "exp_out"), ] camera.set_perspective(ngl.AnimatedVec2(perspective_animkf)) return camera
def compute_animation(cfg): cfg.duration = 5 cfg.aspect_ratio = (1, 1) local_size = 2 vertices_data = array.array('f', [ -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, -0.5, 0.5, 0.0, 0.5, 0.5, 0.0, ]) nb_vertices = 4 input_vertices = ngl.BufferVec3(data=vertices_data, label='vertices') output_vertices = ngl.BufferVec3(data=vertices_data, label='vertices') input_block = ngl.Block(fields=[input_vertices], layout='std140') output_block = ngl.Block(fields=[output_vertices], layout='std140') rotate_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360) ] rotate = ngl.Rotate(ngl.Identity(), axis=(0, 0, 1), anim=ngl.AnimatedFloat(rotate_animkf)) transform = ngl.UniformMat4(transform=rotate) program = ngl.ComputeProgram(_ANIMATION_COMPUTE, workgroup_size=(local_size, local_size, 1)) program.update_properties(dst=ngl.ResourceProps(writable=True)) compute = ngl.Compute(workgroup_count=(nb_vertices / (local_size**2), 1, 1), program=program) compute.update_resources(transform=transform, src=input_block, dst=output_block) quad_buffer = ngl.BufferVec3(block=output_block, block_field=0) geometry = ngl.Geometry(quad_buffer, topology='triangle_strip') program = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) render = ngl.Render(geometry, program) render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['sgreen'])) return ngl.Group(children=(compute, render))
def cropboard(cfg, dim=15): '''Divided media using instancing draw and UV coords offsetting from a buffer''' m0 = cfg.medias[0] random.seed(0) cfg.duration = 10 cfg.aspect_ratio = (m0.width, m0.height) kw = kh = 1. / dim qw = qh = 2. / dim p = ngl.Program(vertex=cfg.get_vert('cropboard'), fragment=cfg.get_frag('texture')) p.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) m = ngl.Media(m0.filename) t = ngl.Texture2D(data_src=m) uv_offset_buffer = array.array('f') translate_a_buffer = array.array('f') translate_b_buffer = array.array('f') q = ngl.Quad(corner=(0, 0, 0), width=(qw, 0, 0), height=(0, qh, 0), uv_corner=(0, 0), uv_width=(kw, 0), uv_height=(0, kh)) for y in range(dim): for x in range(dim): uv_offset = [x * kw, (y + 1.) * kh - 1.] src = [random.uniform(-2, 2), random.uniform(-2, 2)] dst = [x * qw - 1., 1. - (y + 1.) * qh] uv_offset_buffer.extend(uv_offset) translate_a_buffer.extend(src) translate_b_buffer.extend(dst) utime_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration * 2 / 3., 1, 'exp_out') ] utime = ngl.AnimatedFloat(utime_animkf) render = ngl.Render(q, p, nb_instances=dim**2) render.update_frag_resources(tex0=t) render.update_vert_resources(time=utime) render.update_instance_attributes( uv_offset=ngl.BufferVec2(data=uv_offset_buffer), translate_a=ngl.BufferVec2(data=translate_a_buffer), translate_b=ngl.BufferVec2(data=translate_b_buffer), ) return render
def transform_animated_camera(cfg): cfg.duration = 5.0 g = ngl.Group() elems = ( # fmt: off (COLORS.red, None), (COLORS.yellow, (-0.6, 0.8, -1)), (COLORS.green, (0.6, 0.8, -1)), (COLORS.cyan, (-0.6, -0.5, -1)), (COLORS.magenta, (0.6, -0.5, -1)), # fmt: on ) quad = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) for color, vector in elems: node = ngl.RenderColor(color, geometry=quad) if vector: node = ngl.Translate(node, vector=vector) g.add_children(node) g = ngl.GraphicConfig(g, depth_test=True) camera = ngl.Camera(g) camera.set_eye(0, 0, 2) camera.set_center(0.0, 0.0, 0.0) camera.set_up(0.0, 1.0, 0.0) camera.set_clipping(0.1, 10.0) tr_animkf = [ ngl.AnimKeyFrameVec3(0, (0.0, 0.0, 0.0)), ngl.AnimKeyFrameVec3(cfg.duration, (0.0, 0.0, 3.0)) ] eye_transform = ngl.Translate(ngl.Identity(), vector=ngl.AnimatedVec3(tr_animkf)) rot_animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360) ] eye_transform = ngl.Rotate(eye_transform, axis=(0, 1, 0), angle=ngl.AnimatedFloat(rot_animkf)) camera.set_eye_transform(eye_transform) perspective_animkf = [ ngl.AnimKeyFrameVec2(0.5, (60.0, cfg.aspect_ratio_float)), ngl.AnimKeyFrameVec2(cfg.duration, (45.0, cfg.aspect_ratio_float)), ] camera.set_perspective(ngl.AnimatedVec2(perspective_animkf)) return camera
def rtt_clear_attachment_with_timeranges(cfg): cfg.aspect_ratio = (1, 1) # Time-disabled full screen white quad quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) program = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) render = ngl.Render(quad, program) render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['white'])) time_range_filter = ngl.TimeRangeFilter(render) time_range_filter.add_ranges(ngl.TimeRangeModeNoop(0)) # Intermediate no-op RTT to force the use of a different render pass internally texture = ngl.Texture2D(width=32, height=32) rtt_noop = ngl.RenderToTexture(ngl.Identity(), [texture]) # Centered rotating quad quad = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) program = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) render = ngl.Render(quad, program) render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['orange'])) animkf = [ ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, -360) ] render = ngl.Rotate(render, anim=ngl.AnimatedFloat(animkf)) group = ngl.Group(children=(time_range_filter, rtt_noop, render)) # Root RTT texture = ngl.Texture2D(width=512, height=512) rtt = ngl.RenderToTexture(group, [texture]) # Full screen render of the root RTT result quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) render = ngl.Render(quad, program) render.update_frag_resources(tex0=texture) return ngl.Group(children=(rtt, render))
def time_remapping(cfg): ''' Time remapping in the following order: - nothing displayed for a while (but media prefetch happening in background) - first frame displayed for a while - normal playback - last frame displayed for a while (even though the media is closed) - nothing again until the end ''' m0 = cfg.medias[0] media_seek = 10 noop_duration = 1 prefetch_duration = 2 freeze_duration = 1 playback_duration = 5 range_start = noop_duration + prefetch_duration play_start = range_start + freeze_duration play_stop = play_start + playback_duration range_stop = play_stop + freeze_duration duration = range_stop + noop_duration cfg.duration = duration cfg.aspect_ratio = (m0.width, m0.height) media_animkf = [ ngl.AnimKeyFrameFloat(play_start, media_seek), ngl.AnimKeyFrameFloat(play_stop, media_seek + playback_duration), ] q = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) m = ngl.Media(m0.filename, time_anim=ngl.AnimatedFloat(media_animkf)) t = ngl.Texture2D(data_src=m) r = ngl.Render(q) r.update_textures(tex0=t) time_ranges = [ ngl.TimeRangeModeNoop(0), ngl.TimeRangeModeCont(range_start), ngl.TimeRangeModeNoop(range_stop), ] rf = ngl.TimeRangeFilter(r, ranges=time_ranges, prefetch_time=prefetch_duration) return rf
def ret_func(cfg, nb_points=100, zoom=1, offset_start=0, offset_end=1, draw_in=True, draw_out=True, draw_in_out=True, draw_out_in=True): g = ngl.Group() cfg.aspect_ratio = (1, 1) frag_data = cfg.get_frag('color') program = ngl.Program(fragment=frag_data) for idx, ext in enumerate(versions): interp = name if ext is not None: interp += '_' + ext if not eval('draw_' + ext): continue anim = ngl.AnimatedFloat([ ngl.AnimKeyFrameFloat(-1, -1), ngl.AnimKeyFrameFloat(1, 1, interp, easing_start_offset=offset_start, easing_end_offset=offset_end) ]) vertices_data = array.array('f') for i in range(nb_points + 1): x = (i / float(nb_points) * 2 - 1) y = anim.evaluate(x * 1 / zoom) * zoom vertices_data.extend([x, y, 0]) vertices = ngl.BufferVec3(data=vertices_data) geometry = ngl.Geometry(vertices, topology='line_strip') render = ngl.Render(geometry, program) render.update_uniforms(color=ngl.UniformVec4(_colors[idx])) g.add_children(render) return g
def playback_speed(cfg, speed=1.0): '''Adjust media playback speed using animation keyframes''' m0 = cfg.medias[0] media_duration = m0.duration initial_seek = 5 rush_duration = media_duration - initial_seek cfg.duration = rush_duration / speed cfg.aspect_ratio = (m0.width, m0.height) q = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) time_animkf = [ngl.AnimKeyFrameFloat(0, initial_seek), ngl.AnimKeyFrameFloat(cfg.duration, media_duration)] m = ngl.Media(m0.filename, time_anim=ngl.AnimatedFloat(time_animkf)) t = ngl.Texture2D(data_src=m) p = ngl.Program() render = ngl.Render(q, p) render.update_textures(tex0=t) return render
def compute_animation(cfg): cfg.duration = 5 cfg.aspect_ratio = (1, 1) local_size = 2 shader_version = '310 es' if cfg.backend == 'gles' else '430' shader_data = dict( version=shader_version, local_size=local_size, ) compute_shader = _ANIMATION_COMPUTE % shader_data vertex_shader = _ANIMATION_VERT % shader_data fragment_shader = _ANIMATION_FRAG % shader_data vertices_data = array.array('f', [ -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.5, 0.5, 0.0, -0.5, 0.5, 0.0, ]) nb_vertices = 4 input_vertices = ngl.BufferVec3(data=vertices_data) output_vertices = ngl.BufferVec3(data=vertices_data) input_block = ngl.Block(fields=[input_vertices], layout='std140') output_block = ngl.Block(fields=[output_vertices], layout='std430') rotate_animkf = [ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360)] rotate = ngl.Rotate(ngl.Identity(), axis=(0, 0, 1), anim=ngl.AnimatedFloat(rotate_animkf)) transform = ngl.UniformMat4(transform=rotate) program = ngl.ComputeProgram(compute_shader) compute = ngl.Compute(nb_vertices / (local_size ** 2), 1, 1, program) compute.update_uniforms(transform=transform) compute.update_blocks(input_block=input_block, output_block=output_block) quad_buffer = ngl.BufferVec3(block=output_block, block_field=0) geometry = ngl.Geometry(quad_buffer, topology='triangle_fan') program = ngl.Program(vertex=vertex_shader, fragment=fragment_shader) render = ngl.Render(geometry, program) render.update_uniforms(color=ngl.UniformVec4(value=COLORS['sgreen'])) return ngl.Group(children=(compute, render))
def fibo(cfg, n=8): '''Fibonacci with a recursive tree (nodes inherit transforms)''' cfg.duration = 5.0 cfg.aspect_ratio = (1, 1) p = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) fib = [0, 1, 1] for i in range(2, n): fib.append(fib[i] + fib[i - 1]) fib = fib[::-1] shift = 1 / 3. # XXX: what's the exact math here? shape_scale = 1. / ((2. - shift) * sum(fib)) orig = (-shift, -shift, 0) g = None root = None for i, x in enumerate(fib[:-1]): w = x * shape_scale gray = 1. - i / float(n) color = [gray, gray, gray, 1] q = ngl.Quad(orig, (w, 0, 0), (0, w, 0)) render = ngl.Render(q, p) render.update_frag_resources(color=ngl.UniformVec4(value=color)) new_g = ngl.Group() animkf = [ ngl.AnimKeyFrameFloat(0, 90), ngl.AnimKeyFrameFloat(cfg.duration / 2, -90, 'exp_in_out'), ngl.AnimKeyFrameFloat(cfg.duration, 90, 'exp_in_out') ] rot = ngl.Rotate(new_g, anchor=orig, anim=ngl.AnimatedFloat(animkf)) if g: g.add_children(rot) else: root = rot g = new_g new_g.add_children(render) orig = (orig[0] + w, orig[1] + w, 0) return root
def animated_circles(cfg): '''Simple cyclic circles animation''' group = ngl.Group() cfg.duration = 5. cfg.aspect_ratio = (1, 1) radius = 0.2 n = 10 step = 360. / n shape = ngl.Circle(radius=radius, npoints=128) prog = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) render = ngl.Render(shape, prog) render.update_uniforms(color=ngl.UniformVec4([1.0] * 4)) for i in range(n): mid_time = cfg.duration / 2.0 start_time = mid_time / (i + 2) end_time = cfg.duration - start_time scale_animkf = [ ngl.AnimKeyFrameVec3(start_time, (0, 0, 0)), ngl.AnimKeyFrameVec3(mid_time, (1.0, 1.0, 1.0), 'exp_out'), ngl.AnimKeyFrameVec3(end_time, (0, 0, 0), 'exp_in'), ] angle = i * step rotate_animkf = [ ngl.AnimKeyFrameFloat(start_time, 0), ngl.AnimKeyFrameFloat(mid_time, angle, 'exp_out'), ngl.AnimKeyFrameFloat(end_time, 0, 'exp_in'), ] tnode = render tnode = ngl.Scale(tnode, anim=ngl.AnimatedVec3(scale_animkf)) tnode = ngl.Translate(tnode, vector=(1 - radius, 0, 0)) tnode = ngl.Rotate(tnode, anim=ngl.AnimatedFloat(rotate_animkf)) group.add_children(tnode) return group