MicroCity笔记MicroCity笔记
笔记
  • Microcity Desktop 文档
  • Microcity Web 文档
  • 其它

    • 仿真框架(港口)说明文档
    • 模型/库资源
  • GitHub

    • MicroCity Desktop 仓库
    • MicroCity Web 仓库
    • MicroCity Web 在线环境
  • Gitee

    • MicroCity Desktop 仓库
    • MicroCity Web 仓库
  • zhhuu.top (自建修改)

    • MicroCity Web (fork) 仓库
    • MicroCity Web (fork) 在线环境
  • 简体中文
  • English
笔记
  • Microcity Desktop 文档
  • Microcity Web 文档
  • 其它

    • 仿真框架(港口)说明文档
    • 模型/库资源
  • GitHub

    • MicroCity Desktop 仓库
    • MicroCity Web 仓库
    • MicroCity Web 在线环境
  • Gitee

    • MicroCity Desktop 仓库
    • MicroCity Web 仓库
  • zhhuu.top (自建修改)

    • MicroCity Web (fork) 仓库
    • MicroCity Web (fork) 在线环境
  • 简体中文
  • English
  • 目录
  • 通用知识

    • Lua语言快速上手
    • MicroCity的版本
    • 仿真时间推进
    • 面向对象编程
    • 有关工具
  • MicroCity

    • 结果可视化
    • 操作网络
    • 模型求解
  • MicroCityWeb

    • 用户界面简介
    • 3D 场景
    • 3D 对象
    • 离散事件仿真和程序控制
    • 混合整数规划
    • 调试相关
    • 图表绘制功能
  • 思路

    • 自动化仓库仿真思路
    • 通用绘图代码
    • 港口AGV服务流程三维仿真思路
  • Gallery

    • 绘制一个时钟
    • 构建电梯仿真模型
    • 指数拓展的二分搜索
    • 计算复杂度分析

绘制一个时钟

build a clock thumbnail

目标

本文的目标是制作一个时钟,每秒钟时针和分针都会进行非线性运动,并且能够持续循环,这也可以算是一种仿真。

具体的运动如下:

  • 时针每 1 秒为周期旋转 45°,并且每个周期内的速度非线性,先快后慢;
  • 分针每 1 秒为周期旋转 360°,同样以非线性速度运动。

绘制时钟

时针和分针

如何画指针

以绘制时针为例,我们可以通过绘制一个长方形来实现。首先,将时针长方形的长度设置为参数 len_hour;由于时针和分针的宽度相同,都设置为参数 radius。通过添加一个 polygon 对象,我们可以绘制一个长方形。

local hand_minute = scene.addobj("polygon", {
    vertices = {-radius, 0, 0, -radius, len_minute, 0, radius, len_minute, 0, radius, 0, 0},
    size = 0,
    color = 'white'
})

分针同理,只需将 len_hour 替换为 len_minute 即可。

local hand_hour = scene.addobj("polygon", {
    vertices = {-radius, 0, 0, -radius, len_hour, 0, radius, len_hour, 0, radius, 0, 0},
    size = 0,
    color = 'white'
})

背景板和转轴

时钟的背景板和时针的转轴都是圆形,而 MicroCity 中没有提供绘制圆形的函数。但是我们可以通过绘制多边形来模拟圆形。

-- circle
function get_circle_vertices(radius)
    local vertices_circle = {}
    local segment = 360
    local radius_step = math.pi * 2 / segment
    print()
    for i = 1, segment do
        local k = 3 * (i - 1)
        vertices_circle[k + 1], vertices_circle[k + 2], vertices_circle[k + 3] = radius * math.cos(i * radius_step),
            radius * math.sin(i * radius_step), 0
    end

    return vertices_circle
end

这个函数的思路是,将圆分成指定的份数(通过参数 segment 设置),然后通过三角函数计算出每个点的坐标。这样就可以得到一个近似圆形的多边形。

然后我们可以通过这个函数绘制背景板和转轴,转轴已经通过 radius 参数控制其半径大小;背景板通过 radius_bg 参数控制大小。

-- draw background circle
local bg_circle = scene.addobj("polygon", {
    vertices = get_circle_vertices(radius_bg),
    size = 0,
    color = '#fec300'
})
bg_circle:setpos(0, 0, -1)

-- draw pointer core circle
local core_circle = scene.addobj("polygon", {
    vertices = get_circle_vertices(radius),
    size = 0,
    color = 'white'
})

让指针运动

运动函数

我们首先确定时针的运动函数。为了实现时针的非线性运动,我们可以使用三角函数。通过适当的函数变换,我们可以使得时针在每个周期的开始和结束时速度为 0,而在周期中间时速度逐渐加快然后再减慢。

这个函数的基本表达式为:

v=1−cos(8t)v=1-cos(8t) v=1−cos(8t)

时针速度函数

而分针每个周期的运动速度正好是时针的 8 倍,因此分针的运动函数只需在时针运动函数的基础上修改三角函数的参数即可。

v=1−cos(t)v=1-cos(t) v=1−cos(t)

分针速度函数

函数转换

如果 x 表示为时针真实走过的弧度,将上面的运动函数积分可以得到时针 x-t 关系的函数:

x=∫vdt=∫(1−cos8t)dt=t−18sin8tx=\int v dt = \int (1-cos8t) dt = t - \frac{1}{8}sin8t x=∫vdt=∫(1−cos8t)dt=t−81​sin8t

同理,分针的运动函数为:

x=t−sin(t)x=t-sin(t) x=t−sin(t)

设置运动

在 MicroCity 中,通过 setrot() 函数设置时针的旋转角度。由于旋转方向是顺时针,而正方向为逆时针,因此需要对角度进行取反。

得到时针和分针的弧度位置代码如下:

function x_hour(x)
    return -(x - math.sin(x * 8) / 8)
end

function x_minute(x)
    return -(x - math.sin(x))
end

根据上图可知,时针和分针的周期分别为 π/4\pi/4π/4 和 2π2\pi2π。为了与我们构建的目标相对应,我们需要将时间周期(1s)分别缩放为 π/4\pi/4π/4 和 2π2\pi2π,实现与目标周期相对应。

可以在具体调用时进行调整(方便适应未来修改为不同的周期):

local speed = math.pi * 2

-- set rotation
hand_hour:setrot(0, 0, x_hour(t / 8 * speed))
hand_minute:setrot(0, 0, x_minute(t * speed))

与现实时间对应

现在时针和分针已经可以按照给定的输入实现非线性的速度运动了,但是这个运动并没有与现实时间对应起来。

我们可以通过 os.clock() 函数获取当前的时间,并通过记录这个值计算出每次调用这个函数的时间差,以此令仿真时间 t 与真实世界时间相对应。

因此,循环部分可以这样写:

local t = 0
local last_time = os.clock()
while scene.render() do
    -- align with real world time
    t = t + (os.clock() - last_time)
    last_time = os.clock()

    -- set rotation
    hand_hour:setrot(0, 0, x_hour(t / 8 * speed))
    hand_minute:setrot(0, 0, x_minute(t * speed))

    print()
    print('t =', math.floor(t))
end

如果想要检验时针与分针是否真的在1秒时完成一次周期,可以在循环最后通过如下代码检验:

-- 检验1圈为2*pi
if t>=1 then
    break
end

效果和代码

效果

最终实现的仿真效果如下:

🔗 在MicroCityWeb中打开

代码

scene.setenv({
    camtype = 'ortho'
})

-- definitions
local radius = 1
local len_minute = 8
local len_hour = 4
local radius_bg = 12

-- shapes
local hand_minute = scene.addobj("polygon", {
    vertices = {-radius, 0, 0, -radius, len_minute, 0, radius, len_minute, 0, radius, 0, 0},
    size = 0,
    color = 'white'
})
local hand_hour = scene.addobj("polygon", {
    vertices = {-radius, 0, 0, -radius, len_hour, 0, radius, len_hour, 0, radius, 0, 0},
    size = 0,
    color = 'white'
})

-- circle
function get_circle_vertices(radius)
    local vertices_circle = {}
    local segment = 360
    local radius_step = math.pi * 2 / segment
    print()
    for i = 1, segment do
        local k = 3 * (i - 1)
        vertices_circle[k + 1], vertices_circle[k + 2], vertices_circle[k + 3] = radius * math.cos(i * radius_step),
            radius * math.sin(i * radius_step), 0
    end

    return vertices_circle
end

-- draw background circle
local bg_circle = scene.addobj("polygon", {
    vertices = get_circle_vertices(radius_bg),
    size = 0,
    color = '#fec300'
})
bg_circle:setpos(0, 0, -1)

-- draw pointer core circle
local core_circle = scene.addobj("polygon", {
    vertices = get_circle_vertices(radius),
    size = 0,
    color = 'white'
})

function x_hour(x)
    return -(x - math.sin(x * 8) / 8)
end

function x_minute(x)
    return -(x - math.sin(x))
end

local t = 0
local speed = math.pi * 2
local last_time = os.clock()
while scene.render() do
    -- align with real world time
    t = t + (os.clock() - last_time)
    last_time = os.clock()

    -- set rotation
    hand_hour:setrot(0, 0, x_hour(t / 8 * speed))
    hand_minute:setrot(0, 0, x_minute(t * speed))

    print()
    print('t=', math.floor(t))

    -- 检验1圈为2*pi
    -- if t>=1 then
    --     break
    -- end
end

scene.render()
Last Updated:
Contributors: huuhghhgyg
Next
构建电梯仿真模型