跳转至

场景与动作

场景是不可变的节点集合,节点有稳定的编号,另外还有一个相机。动作则是让节点的属性随时间变化。两者都不知道绘图端的存在。

2D 场景

from kinetickit import (
    Animation,
    Bounds2D,
    Group2D,
    Line2D,
    Path2D,
    PathNode2D,
    Scene2D,
    Shape2D,
    Transform2D,
    Vec2,
    ease,
)

x_axis = Line2D(id="x-axis", start=Vec2(0, 0), end=Vec2(10, 0), width=1.5, color="#000000")
y_axis = Line2D(id="y-axis", start=Vec2(0, 0), end=Vec2(0, 8), width=1.5, color="#000000")
curve = PathNode2D.polyline(
    "curve", [(0, 7), (2, 4.5), (4, 3), (7, 1.6), (10, 1)], width=3, color="#000000", draw_progress=0.0
)
area = Shape2D(id="area", path=Path2D.rect(0, 0, 4, 3), fill="#bbbbbb", opacity=0.0)
plot = Group2D(id="plot", children=(x_axis, y_axis, area, curve))

scene = Scene2D(plot)
scene = scene.bind({scene.camera.ref("view"): Bounds2D(Vec2(-1, -1), Vec2(12, 9))})

draw = curve.animate.draw_progress.to(1.0, duration=1.5, easing=ease.in_out_cubic)
fade = area.animate.opacity.to(1.0, duration=0.8)
move = plot.animate.transform.to(Transform2D(translate=Vec2(1, 0.6)), duration=0.8)

Animation(scene, draw.then(fade).then(move)).save("scene2d.svg", width=455, height=315)

路径逐段画出,图形淡入,整个群组移动;接着动作倒着播放。

路径逐段画出,图形淡入,整个群组移动;接着动作倒着播放。

每种节点都能查到哪些属性可以制作动画:Point2D.animatable_properties() 返回属性名称与数值类型,node.animate.<属性> 则为其中一个属性创建动作。颜色、矢量、角度与变换各依自己的规则插值(参见插值与变换)。

Line2D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    color=...,
    width=2.0,
    cap='round',
    join='round',
    dash=(),
    draw_progress=1.0,
    start=Vec2(x=0.0, y=0.0),
    end=Vec2(x=1.0, y=0.0),
)

两点之间的描边线段。端点、颜色、线宽、虚线样式与绘制进度都能制作动画。

PathNode2D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    color=...,
    width=2.0,
    cap='round',
    join='round',
    dash=(),
    draw_progress=1.0,
    path=...,
)

开放或封闭的描边 Path2D。它保留直线与 Bézier 命令,并支持路径变形。PathNode2D.polyline(id, points) 是画直线折线的捷径。

Shape2D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    path=...,
    fill=...,
    fill_rule='nonzero',
    stroke=None,
    stroke_width=1.0,
    join='round',
    dash=(),
)

填色的路径,可另外加描边。用于区域、封闭轮廓与复合图形。

Group2D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    children=(),
    transform=...,
    clip=None,
)

对一串有顺序的子节点,统一套用变换、透明度与裁切的节点。

Text2D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    asset=None,
    position=Vec2(x=0.0, y=0.0),
    rotation=0.0,
    scale=1.0,
    color=...,
)

由排版适配器产生、已定位的 TextAsset,缩放、变换与透明度都能制作动画(参见文本排版)。

Camera2D(
    *,
    id='camera',
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    view=None,
    pan=Vec2(x=0.0, y=0.0),
    zoom=1.0,
)

定义 2D 世界中可见的矩形,以及它如何对应到输出窗口。用 scene.bind({scene.camera.ref("view"): Bounds2D(...)}) 设置。视野会维持长宽比,所以输出尺寸的比例不同时,会出现留白。

3D 场景

import numpy as np

from kinetickit import Animation, Camera3D, DirectionalLight3D, Point3D, Scene3D, Surface3D, Vec3
from kinetickit.styles import ColorScale, Material

xs = ys = np.linspace(-2, 2, 28)
X, Y = np.meshgrid(xs, ys)
Z = np.sin(np.hypot(X, Y) * 2.2) / (1 + np.hypot(X, Y))
surface = Surface3D.height_field(
    "surface", xs, ys, Z, colorscale=ColorScale.viridis_like(), material=Material(0.4, 0.6, 0.2, 24)
)
scene = Scene3D(
    surface,
    Point3D(id="peak", position=Vec3(0, 0, 1.0), size=6, color="#d62728"),
    DirectionalLight3D(id="sun"),
    camera=Camera3D(position=Vec3(5.5, -6.5, 4.2), target=Vec3(0, 0, 0.2)),
)
orbit = scene.camera.animate.position.to(Vec3(-6, -5.5, 4.2), duration=2.0)
Animation(scene, orbit).save("surface.gif", width=320, height=240, fps=10)

相机绕着打光的高度场曲面转动。

相机绕着打光的高度场曲面转动。

Scene3D(*nodes, overlay=(), camera=None)

不可变的 3D 场景,包含几何、光源、群组与一个相机。

Point3D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    position=Vec3(x=0.0, y=0.0, z=0.0),
    size=4.0,
    symbol='circle',
    color=...,
)

3D 空间中的点标记,位置、大小、颜色与透明度都能制作动画。

Line3D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    color=...,
    width=2.0,
    cap='round',
    join='round',
    dash=(),
    draw_progress=1.0,
    start=Vec3(x=0.0, y=0.0, z=0.0),
    end=Vec3(x=1.0, y=0.0, z=0.0),
)

单一 3D 线段,由目前的相机投影。

Polyline3D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    color=...,
    width=2.0,
    cap='round',
    join='round',
    dash=(),
    draw_progress=1.0,
    points=...,
    closed=False,
)

由有顺序的 3D 点组成的折线,画成相连的线段。

Mesh3D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    color=...,
    material=...,
    edge_color=None,
    edge_width=0.5,
    two_sided=True,
    mesh=...,
)

带索引的顶点与面,拓扑会经过验证,可选用每个面的颜色与材质设置。

Surface3D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    color=...,
    material=...,
    edge_color=None,
    edge_width=0.5,
    two_sided=True,
    grid=...,
    colorscale=None,
    value_range=None,
)

由 \(x,y\) 网格与高度值创建的规则高度场曲面。常用的建构方式是 Surface3D.height_field(id, xs, ys, zs)。

Group3D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    children=(),
    transform=...,
)

一组有顺序的 3D 节点,共用同一个变换与透明度。

Camera3D(
    *,
    id='camera',
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    position=Vec3(x=5.0, y=-7.0, z=4.0),
    target=Vec3(x=0.0, y=0.0, z=0.0),
    up=Vec3(x=0.0, y=0.0, z=1.0),
    projection='perspective',
    fov=0.6981317007977318,
    ortho_height=6.0,
    near=0.1,
    far=1000.0,
)

透视或正交相机,有位置、目标、裁切平面与投影设置。它的属性和一般节点一样可以制作动画,所以绕行只是一个 position 动作。

Vec3(x, y, z)
xfloat

x 分量。

yfloat

y 分量。

zfloat

z 分量。

3D 的点或方向。

ColorScale(stops)
stopstuple[tuple[float, Color | str], ...]

由 0 到 1、依序排列的 (位置, 颜色) 配对。

通过有序的色标,把数值对应到颜色。

Material(ambient=0.35, diffuse=0.65, specular=0.0, shininess=32.0)
ambientfloat默认值: 0.35

永远被照亮的比例。

diffusefloat默认值: 0.65

随光线变化的比例。

specularfloat默认值: 0.0

高光强度。

shininessfloat默认值: 32.0

高光的集中程度。

表面的环境光、漫射与镜面反应。

DirectionalLight3D(
    *,
    id,
    opacity=1.0,
    z=0.0,
    visible=True,
    semantics=None,
    direction=Vec3(x=-0.4, y=0.6, z=-0.7),
    color=...,
    intensity=1.0,
)
idstr

稳定的编号。

光线行进的方向。

intensityfloat默认值: 1.0

亮度。

朝单一方向照射的光源,像太阳那样。

组合时间

Motion

不可变的变化描述。动作可以依序、重叠、延迟、重复、反转或裁切。

Timeline(motion, base, registry=...)

动作树编译后的形式,可以直接求出任意有效时间的属性值。

Keyframe(time, value, easing=None, hold=False)

某个时间点的属性值,也可指定通往下一个区间的缓动规则。hold=True 会让这个值维持到下一个关键帧。

Keyframes(target, frames, easing=...)

同一属性的关键帧串行,会检查端点与时间顺序是否合法。

motion = (
    a.animate.position.to(Vec2(5, 5), duration=1.0)
    .then(b.animate.opacity.to(1.0, duration=0.5))
    .with_(c.animate.size.to(9, duration=0.5), offset=1.0)
    .mark("settled")
)

then 在前一段结束时开始下一段。with_ 则在前一段内部的某个偏移时间开始。两者的总长度见时间与缓动。

场景之间的转场

为两个场景创建动作。编号相同的节点会配成一对,并对兼容的属性做插值。

TransitionPolicy(enter='fade', exit='fade')

决定没有对象或结构不兼容的节点要如何进场、退场、交叉淡化或报错。

路径、网格与曲面都有专用的变形工具。自动范式推不出你要的拓扑时,请明确提供对应方式(参见变形)。

评论