跳转至

§7.3 3D 动画#

约 720 个字 126 行代码 预计阅读时间 13 分钟

一、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()

欢迎在评论区留下你的想法


下面没有更多评论啦😆