跳转至

§7.3 3D 动画

字数 720 个   代码 126 行   图片 2 张   阅读时间 4 分钟   访问量

一、3D 对象 API

3D 对象有许许多多方法供我们调用。

1.1 基本 3D 对象的 API

基本 3D 对象的 API 名称都是一样的,它们都是继承自类 _Object3D,都可以进行平移、旋转和缩放。

1.1.1 平移

平移使用方法 translate,平移没有什么特别,就是移动对象在空间中的位置。

1.1.2 旋转

旋转使用方法 rotate,旋转有两个重载,可以旋转绕某一点进行旋转,也可以旋转绕某一条线进行旋转。 两者的参数略有不同,默认情况下是绕原点进行旋转。

1.1.3 缩放

缩放使用方法 scale,缩放没有重载,但是它有一个参数非常有意思,即缩放参照点。将这个点设为原点,那么对象将会以原点为参照进行大小缩放,这样它的大小和位置都会发生改变。但如果我们将其设为改对象的几何中心,则对象只有大小会发生变化,位置不会有改动。

1.1.4 几何中心

此 API 名为 center,调用它不会有任何变化,但它返回该对象的几何中心。可以配合其他 API 来进行使用。

1.2 复杂 3D 对象的 API

其实复杂的 3D 对象本质上就是基本 3D 对象的组合体而已,因此它们在拥有基本 3D 对象的 API 的前提下,还有其他的方法。内置的两种复杂几何体都是继承于类 Geometry,它们本身没有额外的方法,但是 Geometry 相比于基本 3D 对象,增加了一个 append 方法,可以将更多的基本 3D 对象添加到一个几何体实例中去。

二、通过动画类实现 3D 动画

动画类指的就是上一章的 Animation,通过它我们可以很方便地实现一些简单乃至复杂的动画。

有了 3D 对象的 API 之后,我们就可以通过动画类来实现各种各样~花里胡哨~的效果!Animation 的使用方法已在上一章讲述,这里不再赘述。

三、通过 after 方法实现简单动画

除了可以使用 Animation 类之外,我们还可以使用 tkinter 模块中的功能来实现一些简单的功能。

tkinter 模块中的控件都有一个名为 after 的方法,可以让我们快速做出比较简单的动画,实际上,Animation 的底层也是调用的 after,只不过进行了一些简单的封装。

下面是一个简单的示例(如果下面的动图很卡,说明你网页加载比较慢,并非 tkintertools 的问题,耐心等待就好):

gif

是不是眼花缭乱了?

源代码
import math

import tkintertools as tkt
from tkintertools import tools_3d as t3d

root = tkt.Tk('3D Text', 1280, 720)
space = t3d.Space(root, 1280, 720, 0, 0, bg='black', keep=False)

r = 300

O = t3d.Point(space, [0, 0, 0], fill='white', size=3)
X = t3d.Line(space, [0, 0, 0], [1, 0, 0], fill='')
Y = t3d.Line(space, [0, 0, 0], [0, 1, 0], fill='')
Z = t3d.Line(space, [0, 0, 0], [0, 0, 1], fill='')

ring = {'x': [], 'y': [], 'z': []}  # type: dict[str, list[t3d.Text3D]]
line = {'x': [], 'y': [], 'z': []}  # type: dict[str, list[t3d.Text3D]]

for i in range(26):
    t = chr(65+i)
    φ = i/26 * math.tau
    c1 = r * math.sin(φ)
    c2 = r * math.cos(φ)
    ring['x'].append(t3d.Text3D(space, [0, c1, c2], text=t, fill='#FF0000'))
    ring['y'].append(t3d.Text3D(space, [c1, 0, c2], text=t, fill='#00FF00'))
    ring['z'].append(t3d.Text3D(space, [c1, c2, 0], text=t, fill='#0000FF'))

for i in range(10):
    t = str(i)
    c = (i+1) * 600/11 - r
    line['x'].append(t3d.Text3D(space, [c, 0, 0], text=t, fill='#00FFFF'))
    line['y'].append(t3d.Text3D(space, [0, c, 0], text=t, fill='#FF00FF'))
    line['z'].append(t3d.Text3D(space, [0, 0, c], text=t, fill='#FFFF00'))


def animation():
    for obj3D in ring['x']:
        obj3D.rotate(0.05, axis=X.coordinates)
    for obj3D in ring['y']:
        obj3D.rotate(0.05, axis=Y.coordinates)
    for obj3D in ring['z']:
        obj3D.rotate(0.05, axis=Z.coordinates)
    for obj3D in line['x']:
        obj3D.rotate(-0.05, axis=Y.coordinates)
    for obj3D in line['y']:
        obj3D.rotate(-0.05, axis=Z.coordinates)
    for obj3D in line['z']:
        obj3D.rotate(-0.05, axis=X.coordinates)
    for obj3D in space.items_3d():
        obj3D.rotate(0, -0.01, 0.01, center=O.center())
        obj3D.update()
    root.after(10, animation)


animation()
root.mainloop()

或许我们可以用它做一个 3D 的时钟?

png

源代码
import math
import time

import tkintertools as tkt
from tkintertools import tools_3d as t3d

root = tkt.Tk('3D Clock', 1280, 720)
space = t3d.Space(root, 1280, 720, 0, 0, bg='black', keep=False)

O = t3d.Point(space, [0, 0, 0], fill='white', size=3)
X = t3d.Line(space, [0, 0, 0], [1, 0, 0], fill='')
Y = t3d.Line(space, [0, 0, 0], [0, 1, 0], fill='')
Z = t3d.Line(space, [0, 0, 0], [0, 0, 1], fill='')

h = t3d.Line(space, [0, 0, 0], [0, 0, 100], width=6, fill='#FF0000')
m = t3d.Line(space, [0, 0, 0], [0, 0, 150], width=4, fill='#00FF00')
s = t3d.Line(space, [0, 0, 0], [0, 0, 250], width=2, fill='#0000FF')

fill = []
fill += tkt.color(['#FF0000', '#00FF00'], seqlength=20)
fill += tkt.color(['#00FF00', '#0000FF'], seqlength=20)
fill += tkt.color(['#0000FF', '#FF0000'], seqlength=20)

r = 300

for i, c in enumerate(fill):
    t = f'{(i // 5):02d}' if i % 5 == 0 else '●'
    if t == '00':
        t = 12
    size = 30 if i % 5 == 0 else 16
    φ = (15 - i) / 60 * math.tau
    x = r * math.cos(φ)
    y = r * math.sin(φ)
    t3d.Text3D(space, [0, x, y], text=t, fill=c, font=(tkt.FONT, -size))


time_ = list(map(int, time.strftime('%H %M %S', time.localtime()).split()))
h_ = time_[0] % 12
m_ = time_[1]
s_ = time_[2]

h.rotate((60 * ((60 * h_) + m_) + s_) * -
         math.tau / 60 / 60 / 12, axis=X.coordinates)
m.rotate(((60 * m_) + s_) * -math.tau / 60 / 60, axis=X.coordinates)
s.rotate(s_ * -math.tau / 60, axis=X.coordinates)


def animation(speed: int, t: int = 60*((60*h_) + m_) + s_) -> None:
    """动画"""
    s.rotate(-math.tau / 60, axis=X.coordinates)
    space.itemconfigure(s.item, fill=fill[(t % 60)])
    s.update()
    m.rotate(-math.tau / 60 / 60, axis=X.coordinates)
    space.itemconfigure(m.item, fill=fill[((t // 60) % 60)])
    m.update()
    h.rotate(-math.tau / 60 / 60 / 12, axis=X.coordinates)
    space.itemconfigure(h.item, fill=fill[((t // 60 // 12) % 60)])
    h.update()
    space.after(1000 // speed, animation, speed, t + 1)


def start() -> None:
    """校正时间"""
    while not math.isclose(time.time(), int(time.time())):
        continue
    animation(1)  # 参数为时间流逝速度


start()
root.mainloop()