一个NOIP普及组C++水平的瞎搞记录(



0x01 前言

目前我仅能处理一些简单的动画
例如3个方向的平移与旋转
以及生成静止的帧
并且变换对象是一些简单的几何体
$($换句话说就是需要有现成的一份黑线图案$)$
之后可能会加入其他功能


0x02 大致思路

要做这种动画首先需要有模型
对此我的处理方法就是将若干条黑线作为程序的输入
并转换为拥有统一单位长度的坐标
之后便可以用矩阵来对其进行旋转以及平移操作了
选取C++作为本项目语言


0x03 探索


0.一些变量/常量/参数

(不知道在哪写 就放在这吧)

//用于记录导入arc个数
int idx_mEnd = 0;

//设置原点
float x_0 = 0.5, y_0 = 0.5, z_0 = 0;

//旋转中心(暂时未启用)
//float xO = 0, yO = 0, zO = 0;

//动画起始时间
int ti;

//总动画已播放时间
int tt = 0;

//framerate帧率
int fps;

//动画实际时间起始点
//即输出方式1中arc的实际起始时间
int ts = 100000;

//帧残留(每画面所占帧数)
int n = 1;

//基础BPM
float bpm;

//动画BPM
float abpm;

//变换比例参数 (以y为参照)
//即x = x' * rx  y = y'  t = z' * rz
const double rx = 0.5294117647058823;
const double rz = -34;

//每帧时间
float dt;

//动画内容在屏幕前面的位置(bpm·ms)
float offset = 7500;

//输出方式(目前可为1或2)
//1 质量较高 卡顿大 电脑用
//2 理论上没问题 但是Arcade渲染时会有残影 卡顿小 移动设备使用
int ptype;

1.arc与真实图像大小的转换

3个描述位置的变量x0,y0,t0
很不巧的是他们的单位长度并没有统一
所以第一件事是找到一组比例 使这3个值可以放到一个单位长度相等的坐标系中
这里我选取y坐标作为基准
分别得出x与z轴的比例关系

$$ \begin{aligned} x &= x0 \div rx = x0 \div \frac{450}{850} \\ y &= y0 \\ z &= z0 \div rz = t0 \div -34 \\ \end{aligned}\\ $$

比例参数

值得注意的时rz的取值与音符流速有关
-34仅在流速为4.5时取到
另外x,y分别沿右、上为正
为了构成右手系 z方向需垂直屏幕向外
该方向与t增长方向相反 故rz为负数
为了方便后续计算 我们将(x,y,z)这样一个坐标点表示为矩阵m

$$ m = \begin{bmatrix} x\\ y\\ z\\ \end{bmatrix} $$


2.模型建立

对于一个模型而言 其本质是一些黑线
但是有一点不能忽视
如果采用上文说到的方法 必定有一个原点
因此我们规定导入模型时必须规定一个 中心时间t0
我在这个项目中手动将平面内的 原点设置为了 (0.5,0.5)
相当于这些arc是以 (0.5,0.5,t0)为中心写的

e.g. 一个以(0.5,0.5,100000)为中心各边平行于坐标轴 边长为0.7的正方形
模型对应的arc语句应该长这样:

arc(99987,99987,0.31,0.31,s,0.15,0.85,0,none,true);
arc(99987,99987,0.69,0.69,s,0.15,0.85,0,none,true);
arc(99987,99987,0.31,0.69,s,0.15,0.15,0,none,true);
arc(99987,99987,0.31,0.69,s,0.85,0.85,0,none,true);
arc(99987,100012,0.31,0.31,s,0.15,0.15,0,none,true);
arc(99987,100012,0.69,0.69,s,0.85,0.85,0,none,true);
arc(99987,100012,0.31,0.31,s,0.85,0.85,0,none,true);
arc(99987,100012,0.69,0.69,s,0.15,0.15,0,none,true);
arc(100012,100012,0.31,0.31,s,0.15,0.85,0,none,true);
arc(100012,100012,0.69,0.69,s,0.15,0.85,0,none,true);
arc(100012,100012,0.31,0.69,s,0.15,0.15,0,none,true);
arc(100012,100012,0.31,0.69,s,0.85,0.85,0,none,true);

一个长方体需要12条arc
模型更复杂的时候显然会比较麻烦
在这附上一个可生成长方体的python小脚本

代码有些多 点击展开

# 设定相对原点坐标
# 说明:长方体中心(xa,ya,za)坐标
# (0,0,0)为谱面中心点位置 即(0,0,0)=>(0.50,0.50,t0)
# 注意:调整xa,ya,za坐标时 3个值已调整为单位长度视觉效果一致
# —————— 修改区 ——————
xa = 0
ya = 0
za = 0
t0 = 1000
# ———————————————————
# x轴 t0轴 比例系数
rx = 450 / 850
rz = 34
# 这里没有负号是因为仅导入时需要考虑z的正负

# 创建长方体
def create(a, b, c):
    print("arc(" + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa - a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa - a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya - b / 2, '.2f')) + "," + str(format(0.5 + ya + b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa + a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa + a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya - b / 2, '.2f')) + "," + str(format(0.5 + ya + b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa - a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa + a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya - b / 2, '.2f')) + "," + str(format(0.5 + ya - b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa - a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa + a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya + b / 2, '.2f')) + "," + str(format(0.5 + ya + b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa - a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa - a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya - b / 2, '.2f')) + "," + str(format(0.5 + ya + b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa + a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa + a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya - b / 2, '.2f')) + "," + str(format(0.5 + ya + b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa - a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa + a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya - b / 2, '.2f')) + "," + str(format(0.5 + ya - b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa - a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa + a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya + b / 2, '.2f')) + "," + str(format(0.5 + ya + b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa - a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa - a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya - b / 2, '.2f')) + "," + str(format(0.5 + ya - b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa + a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa + a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya + b / 2, '.2f')) + "," + str(format(0.5 + ya + b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa - a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa - a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya + b / 2, '.2f')) + "," + str(format(0.5 + ya + b / 2, '.2f')) + ",0,none,true);")
    print("arc(" + str(int(t0 + rz * (za - c / 2) + 0.5)) + "," + str(int(t0 + rz * (za + c / 2) + 0.5)) + "," + str(
        format(0.5 + rx * (xa + a / 2), '.2f')) + "," + str(format(0.5 + rx * (xa + a / 2), '.2f')) + ",s," + str(
        format(0.5 + ya - b / 2, '.2f')) + "," + str(format(0.5 + ya - b / 2, '.2f')) + ",0,none,true);")

# creat(长,宽,高)
create(0.7, 0.7, 0.7)


3.模型导入

处理上述模型 我声明了一个arc结构体

arc结构体

t01,t02,x01,x02,y01,y02为arc语句中的变量用于存储直接输入的数据以及作为输出时的中间变量
x1,x2,y1,y2,z1,z2为中间变量 相当于临时存储上文提到的计算结果相对坐标(x,y,z)
Matrix为矩阵 定义如下

矩阵结构体

为了实现本项目的功能 有这么几个操作Matrix的函数
矩阵初始化$($给定row,col生成一个全部填充为0的矩阵$)$InitMatrix(row,col)
用于实现矩阵加法 add(m1,m2)会返回一个Matrix作为结果
用于实现矩阵乘法 mul(m1,m2)会返回一个Matrix作为结果

arc,Matrix及相关函数

//arc结构体
struct arc {
    int t01, t02;
    float x01, x02, y01, y02;
    float x1, x2, y1, y2, z1, z2;
    Matrix m1, m2, m3, m4;
} model[1001];

//定义矩阵结构体
typedef struct {
    int row, col;
    float **matrix;
} Matrix;

//初始化一个行为row列为col矩阵
Matrix InitMatrix(int row, int col) {
    Matrix m;
    float **matrix ;
    matrix = (float **) malloc(row * sizeof(float *)) ;
    for (int i = 0; i < row; i++)
        matrix[i] = (float *)malloc(col * sizeof(float));
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            matrix[i][j] = 0;
        }
    }
    m.col = col;
    m.row = row;
    m.matrix = matrix;
    return m;
}

//矩阵加法
Matrix add(Matrix m1, Matrix m2) {
    for (int i = 0; i < m1.row; i++) {
        for (int j = 0; j < m1.col; j++) {
            m1.matrix[i][j] = m1.matrix[i][j] + m2.matrix[i][j];
        }
    }
    return m1;
}

//矩阵乘法时计算行列向量内积
float calRowCol(Matrix M1, Matrix M2, int row, int col) { //row为M1的行 col为m2的列
    float result = 0;
    int same = M1.col;
    for (int j = 0; j < same; j++) {
        result += M1.matrix[row][j] * M2.matrix[j][col];
    }

    return result;
}

//矩阵乘法
Matrix mul(Matrix m1, Matrix m2) {
    Matrix result = InitMatrix(m1.row, m2.col);
    for (int i = 0; i < m1.row; i++) {
        for (int j = 0; j < m2.col; j++) {
            result.matrix[i][j] = calRowCol(m1, m2, i, j);
        }
    }
    return result;
}

//输出矩阵(调试用)
void printMatrix(Matrix m) {
    for (int i = 0; i < m.row; i++) {
        for (int j = 0; j < m.col; j++) {
            cout << m.matrix[i][j] << "  ";
        }
        cout << endl;
    }
}

关于C++矩阵部分的内容参考https://blog.csdn.net/u011463794/article/details/85223844

导入模型:

模型导入部分

这是一条普通的arc:
arc(t01,t02,x01,x02,s,y01,y02,0,none,true);
每一条arc可计算出2个三维坐标(x1,y1,z1)与(x2,y2,z2)
将其存储到两个矩阵m1,m2中 这两个矩阵仅参与 转动变换
而m3,m4初始值均为(0,0,0) 这两个矩阵仅参与 平动变换
之所以采用这两个变换分解进行的原因其实是我还没解决转动平动同时进行时旋转轴如何固定的问题(
因此我们得到了4个参与变换运算的矩阵,以进行后续操作:

$$ m1 = \begin{bmatrix} x1\\ y1\\ z1\\ \end{bmatrix}, m2 = \begin{bmatrix} x2\\ y2\\ z2\\ \end{bmatrix}, m3 = \begin{bmatrix} 0\\ 0\\ 0\\ \end{bmatrix}, m4 = \begin{bmatrix} 0\\ 0\\ 0\\ \end{bmatrix}\\ $$


4.模型变换

a.平移与旋转

定义move()函数用于实现平动和转动,调用具体参数为
move(dx,dy,dz,dθx,dθy,dθz,t);
$dx,dy,dz$为平移变化量
分别除以$t$可得出速度$vx,vy,vz$
$d \theta x, d \theta y, d \theta z$分别为绕3个坐标轴旋转的角度变化量
分别除以$t$可得出角速度$\omega x,\omega y,\omega z $
单帧时间为$dt = 1000 \div fps$
根据三维坐标变换,得出$dt$时间内的平移变化矩阵:

$$ trans = \begin{bmatrix} vx \times dt\\ vy \times dt\\ vz \times dt\\ \end{bmatrix}\\ $$

转动变化矩阵:

$$ \begin{aligned} rX &= \begin{bmatrix} 1&0&0\\ 0&\cos{(\omega x\times dt)}&\sin{(\omega x\times dt)}\\ 0&-\sin{(\omega x\times dt)}&\cos{(\omega x\times dt)}\\ \end{bmatrix} \\ \\ rY &= \begin{bmatrix} \cos{(\omega x\times dt)}&0&-\sin{(\omega x\times dt)}\\ 0&1&0\\ \sin{(\omega x\times dt)}&0&\cos{(\omega x\times dt)}\\ \end{bmatrix} \\ \\ rZ &= \begin{bmatrix} \cos{(\omega x\times dt)}&\sin{(\omega x\times dt)}&0\\ -\sin{(\omega x\times dt)}&\cos{(\omega x\times dt)}&0\\ 0&0&1\\ \end{bmatrix} \\ \end{aligned}\\ $$

所以每过$dt$,都要对m1,m2,m3,m4做出如下操作:

$$ \begin{aligned} m1 &= rZ \times rY \times rX \times m1\\ m2 &= rZ \times rY \times rX \times m2\\ m3 &= trans + m3\\ m4 &= trans + m4\\ \end{aligned}\\ $$

move()函数中有

//t3为当前动画已播放时间
double t3 = 0;

每一帧都通过调用printArc(t3)来输出

上述过程可通过while循环实现

move()函数

//平移旋转
void move(float _dx, float _dy, float _dz, float _thx, float _thy, float _thz, int _t) {
    float dx = _dx, dy = _dy, dz = _dz, thx = _thx, thy = _thy, thz = _thz;
    int t = _t;
    //printf("%f %f %f %f %f %f %d\n", dx, dy, dz, thx, thy, thz, t);

    //计算速度
    double vx = dx / t;
    double vy = dy / t;
    double vz = dz / t;

    //计算角速度 ms^-1
    double wx = thx * pi / 180 / (float)t;
    double wy = thy * pi / 180 / (float)t;
    double wz = thz * pi / 180 / (float)t;

    //生成平动变化矩阵
    Matrix trans = InitMatrix(3, 1);
    trans.matrix[0][0] = vx * dt;
    trans.matrix[1][0] = vy * dt;
    trans.matrix[2][0] = vz * dt;

    //生成转动变化矩阵
    Matrix rX = InitMatrix(3, 3);
    Matrix rY = InitMatrix(3, 3);
    Matrix rZ = InitMatrix(3, 3);

    rX.matrix[0][0] = 1, rX.matrix[0][1] = 0, rX.matrix[0][2] = 0;
    rX.matrix[1][0] = 0, rX.matrix[1][1] = cos(wx * dt), rX.matrix[1][2] = sin(wx * dt);
    rX.matrix[2][0] = 0, rX.matrix[2][1] = -sin(wx * dt), rX.matrix[2][2] = cos(wx * dt);

    rY.matrix[0][0] = cos(wy * dt), rY.matrix[0][1] = 0, rY.matrix[0][2] = -sin(wy * dt);
    rY.matrix[1][0] = 0, rY.matrix[1][1] = 1, rY.matrix[1][2] = 0;
    rY.matrix[2][0] = sin(wy * dt), rY.matrix[2][1] = 0, rY.matrix[2][2] = cos(wy * dt);

    rZ.matrix[0][0] = cos(wz * dt), rZ.matrix[0][1] = sin(wz * dt), rZ.matrix[0][2] = 0;
    rZ.matrix[1][0] = -sin(wz * dt), rZ.matrix[1][1] = cos(wz * dt), rZ.matrix[1][2] = 0;
    rZ.matrix[2][0] = 0, rZ.matrix[2][1] = 0, rZ.matrix[2][2] = 1;

    //printMatrix(rY);

    //t3为当前动画已播放时间
    double t3 = 0;
    
    //变化循环
    while (t3 <= t) {
        printArc(tt + t3);
        for (int i = 0; i < idx_mEnd; i++) {
            model[i].m1 = mul(rZ, mul(rY, mul(rX, model[i].m1)));
            model[i].m2 = mul(rZ, mul(rY, mul(rX, model[i].m2)));
            model[i].m3 = add(model[i].m3, trans);
            model[i].m4 = add(model[i].m4, trans);
        }
        t3 += dt;
    }
    printArc(tt + t3);
    tt += t;
}

b.静止帧

对于一个静止的画面,实现起来就相对简单了
调用函数motionless(t);
其中t为持续时间
由于该函数包含输出部分,所以放到下一个部分讲解


5.动画

a.基于timinggroup$($$)$的三维逐帧动画原理

由于timing中的bpm是个比较抽象的概念 不过可以理解为速度
为了刻画一段note在铺面中的位置
我们需要引入一个表示位移的量
速度(bpm)持续时间(ms)相乘
得到一个相当于位移的东西,其单位为bpm·ms 简称bms(
我们输出的模型必须要写在一个普通的timing中,才不会发生变形
即此timing中的bpm基础bpm(曲目bpm)
每一帧动画里的黑线会在这一帧出现的时候到达屏幕前,并以0bpmtiming停留dt*n的时间
相当于这一时刻的判定先与黑线实际写到的位置之间的位移是0bms
这里需要注意 实际位置其实是黑线真实时间前某个timing的位置
这将决定黑线动画播放时与判定线的距离
我们假设动画中这一帧出现的时间a,实际位置b 则有

$$ \int_a^b bpm(t) \times dt = 0 \tag{1} $$

由此得出t = a+dt*n之后的timing需要满足的条件
显然我们不希望这一帧的动画在t = a之前就被看到
因此我们要在t = a之前产生一个较大的位移
比如在t = a往前若干毫秒的位置写一个几万bpm的timing
同理在这一帧结束时要让这一帧迅速离场
则在t = a+dt*n处写一个很大的timing
或者使用3.5.3之后的特性hidegroup
scenecontrol(a+n*dt,hidegroup,0.00,1);
之后的timing只要能满足上面的(1)
一帧的动画就完成了
由于较复杂的timing以及重叠的黑线
该动画仅可用timinggroup实现

b.生成timinggroup$($$)$帧平移旋转动画

前文提到过printArc()函数
其实这个函数长这样:

printArc函数

根据ptype的取值$($目前仅有1或2$)$ 选择不同的输出方案

方案1

将黑线写在很远的地方
用Arcade查看效果比较漂亮
但是由于同时加载的arc越来越多 导入手机会卡爆

根据以上结论 可知单帧时间组应该长这样

timinggroup(noinput){
  timing(0,bpm,4.00);
  timing(t3 + ti - 999,50000.00,4.00);
  timing(t3 + ti,0.00,4.00);
  timing(t3 + ti + n * dt,abpm,4.00);
  scenecontrol(t3 + ti + n * dt,hidegroup,0.00,1);
  timing(t3 + ti + n * dt + 10,-1.00,4.00);
  timing(t3 + ti + n * dt + 10 + 10000 - offset,0.00,4.00);
  timing(t3 + ts - 1010,-abpm,4.00);
  timing(t3 + ts - 10000 / bpm,bpm,4.00);
  arc语句
  ......
};

将arc的数据从矩阵中读取出来就好了
另外需要将一些变换后t01>t02的arc的坐标进行swap

方案1代码

//方案1
void printArc1(float t3) {
    printf("timinggroup(noinput){\n");
    printf("  timing(0,%.2f,4.00);\n", bpm);
    printf("  timing(%d,50000.00,4.00);\n", int(t3 + ti - 999 + 0.5));
    printf("  timing(%d,0.00,4.00);\n", int(t3 + ti + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(t3 + ti + n * dt + 0.5), abpm);
    printf("  scenecontrol(%d,hidegroup,0.00,1);\n", int(t3 + ti + n * dt + 0.5));
    printf("  timing(%d,-1.00,4.00);\n", int(t3 + ti + n * dt + 10 + 0.5));
    printf("  timing(%d,0.00,4.00);\n", int(t3 + ti + n * dt + 10 + 10000 - offset + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(t3 + ts - 10000 / bpm - 10 + 0.5), -abpm);
    printf("  timing(%d,%.2f,4.00);\n", int(t3 + ts - 10000 / bpm + 0.5), bpm);
    //坐标-->实际arc数据 (平动+转动)
    for (int idx = 0; idx < idx_mEnd; idx++) {
        model[idx].x01 = (model[idx].m3.matrix[0][0] + model[idx].m1.matrix[0][0]) * rx + x_0;
        model[idx].y01 = (model[idx].m3.matrix[1][0] + model[idx].m1.matrix[1][0]) + y_0;
        model[idx].t01 = (model[idx].m3.matrix[2][0] + model[idx].m1.matrix[2][0]) * rz + t3 + ts + 0.5;
        model[idx].x02 = (model[idx].m4.matrix[0][0] + model[idx].m2.matrix[0][0]) * rx + x_0;
        model[idx].y02 = (model[idx].m4.matrix[1][0] + model[idx].m2.matrix[1][0]) + y_0;
        model[idx].t02 = (model[idx].m4.matrix[2][0] + model[idx].m2.matrix[2][0]) * rz + t3 + ts + 0.5;
        if (model[idx].t01 > model[idx].t02) {
            swap(model[idx].t01, model[idx].t02);
            swap(model[idx].x01, model[idx].x02);
            swap(model[idx].y01, model[idx].y02);
        }
        printf("  arc(%d,%d,%.2f,%.2f,s,%.2f,%.2f,0,none,true);\n", model[idx].t01, model[idx].t02, model[idx].x01,
               model[idx].x02,
               model[idx].y01, model[idx].y02);
    }
    printf("};\n");
}

方案2

将黑线写在很近的地方
这样随着动画播放 一些arc会到屏幕后面去
同时加载的黑线数量大大降低
但是60帧的动画用移动端加载会出现变形的状况$($目前还不知道原因$)$

仅对方案1做出小改动

timinggroup(noinput){
  timing(0,bpm,4.00);
  timing(1,abpm,4.00);
  timing(t3 + ti,0.00,4.00);
  timing(t3 + ti + n * dt,-1.00,4.00);
  scenecontrol(t3 + ti + n * dt,hidegroup,0.00,1);
  timing(t3 + ti + n * dt + 1,bpm,4.00);
  arc语句(算法修改)
  ......
};

方案2代码

//方案2
void printArc2(float t3) {
    printf("timinggroup(noinput){\n");
    printf("  timing(0,%.2f,4.00);\n", bpm);
    printf("  timing(1,%.2f,4.00);\n", abpm);
    printf("  timing(%d,0.00,4.00);\n", int(t3 + ti + 0.5));
    printf("  timing(%d,-1.00,4.00);\n", int(t3 + ti + n * dt + 0.5));
    printf("  scenecontrol(%d,hidegroup,0.00,1);\n", int(t3 + ti + n * dt + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(t3 + ti + n * dt + 1 + 0.5), bpm);
    //坐标-->实际arc数据 (平动+转动)
    for (int idx = 0; idx < idx_mEnd; idx++) {
        model[idx].x01 = (model[idx].m3.matrix[0][0] + model[idx].m1.matrix[0][0]) * rx + x_0;
        model[idx].y01 = (model[idx].m3.matrix[1][0] + model[idx].m1.matrix[1][0]) + y_0;
        model[idx].t01 = (model[idx].m3.matrix[2][0] + model[idx].m1.matrix[2][0]) * rz + t3 + ti + n * dt + offset / bpm + 0.5;
        model[idx].x02 = (model[idx].m4.matrix[0][0] + model[idx].m2.matrix[0][0]) * rx + x_0;
        model[idx].y02 = (model[idx].m4.matrix[1][0] + model[idx].m2.matrix[1][0]) + y_0;
        model[idx].t02 = (model[idx].m4.matrix[2][0] + model[idx].m2.matrix[2][0]) * rz + t3 + ti + n * dt + offset / bpm + 0.5;
        if (model[idx].t01 > model[idx].t02) {
            swap(model[idx].t01, model[idx].t02);
            swap(model[idx].x01, model[idx].x02);
            swap(model[idx].y01, model[idx].y02);
        }
        printf("  arc(%d,%d,%.2f,%.2f,s,%.2f,%.2f,0,none,true);\n", model[idx].t01, model[idx].t02, model[idx].x01,
               model[idx].x02,
               model[idx].y01, model[idx].y02);
    }
    printf("};\n");
}

c.生成timinggroup$($$)$静止帧

使用motionless(t)调用

即忽略dt直接将0.00timing持续t时间

静止帧代码

//静止帧
void motionless(int t) {
    printf("timinggroup(noinput){\n");
    printf("  timing(0,%.2f,4.00);\n", bpm);
    printf("  timing(%d,50000.00,4.00);\n", int(tt + ti - 999 + 0.5));
    printf("  timing(%d,0.00,4.00);\n", int(tt + ti + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(tt + ti + t + 0.5), abpm);
    printf("  scenecontrol(%d,hidegroup,0.00,1);\n", int(tt + ti + t + 0.5));
    printf("  timing(%d,-1.00,4.00);\n", int(tt + ti + t + 10 + 0.5));
    printf("  timing(%d,0.00,4.00);\n", int(tt + ti + t + 10 + 10000 - offset + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(tt + ts - 10000 / bpm - 10 + 0.5), -abpm);
    printf("  timing(%d,%.2f,4.00);\n", int(tt + ts - 10000 / bpm + 0.5), bpm);
    //坐标-->实际arc数据 (平动+转动)
    for (int idx = 0; idx < idx_mEnd; idx++) {
        model[idx].x01 = (model[idx].m3.matrix[0][0] + model[idx].m1.matrix[0][0]) * rx + x_0;
        model[idx].y01 = (model[idx].m3.matrix[1][0] + model[idx].m1.matrix[1][0]) + y_0;
        model[idx].t01 = (model[idx].m3.matrix[2][0] + model[idx].m1.matrix[2][0]) * rz + tt + ts;
        model[idx].x02 = (model[idx].m4.matrix[0][0] + model[idx].m2.matrix[0][0]) * rx + x_0;
        model[idx].y02 = (model[idx].m4.matrix[1][0] + model[idx].m2.matrix[1][0]) + y_0;
        model[idx].t02 = (model[idx].m4.matrix[2][0] + model[idx].m2.matrix[2][0]) * rz + tt + ts;
        if (model[idx].t01 > model[idx].t02) {
            swap(model[idx].t01, model[idx].t02);
            swap(model[idx].x01, model[idx].x02);
            swap(model[idx].y01, model[idx].y02);
        }
        printf("  arc(%d,%d,%.2f,%.2f,s,%.2f,%.2f,0,none,true);\n", model[idx].t01, model[idx].t02, model[idx].x01,
               model[idx].x02,
               model[idx].y01, model[idx].y02);
    }
    printf("};\n");
    tt += t;
}


0x04 基本完成

先附上完整的cpp代码

完整代码

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <malloc.h>
#define pi 3.141592653589793
using namespace std;

//定义矩阵结构体
typedef struct {
    int row, col;
    float **matrix;
} Matrix;

//初始化一个行为row列为col矩阵
Matrix InitMatrix(int row, int col) {
    Matrix m;
    float **matrix ;
    matrix = (float **) malloc(row * sizeof(float *)) ;
    for (int i = 0; i < row; i++)
        matrix[i] = (float *)malloc(col * sizeof(float));
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            matrix[i][j] = 0;
        }
    }
    m.col = col;
    m.row = row;
    m.matrix = matrix;
    return m;
}

//矩阵加法
Matrix add(Matrix m1, Matrix m2) {
    for (int i = 0; i < m1.row; i++) {
        for (int j = 0; j < m1.col; j++) {
            m1.matrix[i][j] = m1.matrix[i][j] + m2.matrix[i][j];
        }
    }
    return m1;
}

//矩阵乘法时计算行列向量内积
float calRowCol(Matrix M1, Matrix M2, int row, int col) { //row为M1的行 col为m2的列
    float result = 0;
    int same = M1.col;
    for (int j = 0; j < same; j++) {
        result += M1.matrix[row][j] * M2.matrix[j][col];
    }

    return result;
}

//矩阵乘法
Matrix mul(Matrix m1, Matrix m2) {
    Matrix result = InitMatrix(m1.row, m2.col);
    for (int i = 0; i < m1.row; i++) {
        for (int j = 0; j < m2.col; j++) {
            result.matrix[i][j] = calRowCol(m1, m2, i, j);
        }
    }
    return result;
}

void printMatrix(Matrix m) {
    for (int i = 0; i < m.row; i++) {
        for (int j = 0; j < m.col; j++) {
            cout << m.matrix[i][j] << "  ";
        }
        cout << endl;
    }
}

//定义结构体 用于存储单条arc数据
//arc(t01,t02,x01,x02,s,y01,y02,0,none,true);
//换算出坐标的值x1,x2,y1,y2,z1,z2 (单位长度统一 为相对原点的坐标)
//分别定义为矩阵m1,m2存储转动参量 m3,m4存储平动参量
//model数组用于存放模型
struct arc {
    int t01, t02;
    float x01, x02, y01, y02;
    float x1, x2, y1, y2, z1, z2;
    Matrix m1, m2, m3, m4;
} model[1001];

int idx_mEnd = 0;

//设置原点
float x_0 = 0.5, y_0 = 0.5, z_0 = 0;

//旋转中心
float xO = 0, yO = 0, zO = 0;

//动画起始时间
int ti;

//总动画已播放时间
int tt = 0;

//framerate
int fps;

//动画实际时间起始点
int ts = 100000;

//残留帧(每画面所占帧数)
int n = 1;

//基础BPM
float bpm;

//动画BPM
float abpm;

//变换比例参数 (以y为参照)
//即x = x' * rx  y = y'  t = z' * rz
const double rx = 0.5294117647058823;
const double rz = -34;

//每帧时间
float dt;

//动画内容在屏幕前面的位置(bpm·ms)
float offset = 7500;

//输出方式(目前可为1或2)
//1 质量较高 卡顿大 电脑用
//2 理论上没问题 但是Arcade渲染时会有残影 卡顿小 移动设备使用
int ptype;

//输出arc
/*
arc时间为t3 + ts
arc展示时间为t3 + ti
输出结果应为:
timinggroup(noinput){
  timing(0,bpm,4.00);
  timing(t3 + ti - 999,50000.00,4.00);
  timing(t3 + ti,0.00,4.00);
  timing(t3 + ti + n * dt,abpm,4.00);
  scenecontrol(t3 + ti + n * dt,hidegroup,0.00,1);
  timing(t3 + ti + n * dt + 10,-1.00,4.00);
  timing(t3 + ti + n * dt + 10 + 10000 - offset,0.00,4.00);
  timing(t3 + ts - 1010,-abpm,4.00);
  timing(t3 + ts - 10000 / bpm,bpm,4.00);
  arc语句
  ......
};
*/

//方案1
void printArc1(float t3) {
    printf("timinggroup(noinput){\n");
    printf("  timing(0,%.2f,4.00);\n", bpm);
    printf("  timing(%d,50000.00,4.00);\n", int(t3 + ti - 999 + 0.5));
    printf("  timing(%d,0.00,4.00);\n", int(t3 + ti + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(t3 + ti + n * dt + 0.5), abpm);
    printf("  scenecontrol(%d,hidegroup,0.00,1);\n", int(t3 + ti + n * dt + 0.5));
    printf("  timing(%d,-1.00,4.00);\n", int(t3 + ti + n * dt + 10 + 0.5));
    printf("  timing(%d,0.00,4.00);\n", int(t3 + ti + n * dt + 10 + 10000 - offset + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(t3 + ts - 10000 / bpm - 10 + 0.5), -abpm);
    printf("  timing(%d,%.2f,4.00);\n", int(t3 + ts - 10000 / bpm + 0.5), bpm);
    //坐标-->实际arc数据 (平动+转动)
    for (int idx = 0; idx < idx_mEnd; idx++) {
        model[idx].x01 = (model[idx].m3.matrix[0][0] + model[idx].m1.matrix[0][0]) * rx + x_0;
        model[idx].y01 = (model[idx].m3.matrix[1][0] + model[idx].m1.matrix[1][0]) + y_0;
        model[idx].t01 = (model[idx].m3.matrix[2][0] + model[idx].m1.matrix[2][0]) * rz + t3 + ts + 0.5;
        model[idx].x02 = (model[idx].m4.matrix[0][0] + model[idx].m2.matrix[0][0]) * rx + x_0;
        model[idx].y02 = (model[idx].m4.matrix[1][0] + model[idx].m2.matrix[1][0]) + y_0;
        model[idx].t02 = (model[idx].m4.matrix[2][0] + model[idx].m2.matrix[2][0]) * rz + t3 + ts + 0.5;
        if (model[idx].t01 > model[idx].t02) {
            swap(model[idx].t01, model[idx].t02);
            swap(model[idx].x01, model[idx].x02);
            swap(model[idx].y01, model[idx].y02);
        }
        printf("  arc(%d,%d,%.2f,%.2f,s,%.2f,%.2f,0,none,true);\n", model[idx].t01, model[idx].t02, model[idx].x01,
               model[idx].x02,
               model[idx].y01, model[idx].y02);
    }
    printf("};\n");
}

//方案2
void printArc2(float t3) {
    printf("timinggroup(noinput){\n");
    printf("  timing(0,%.2f,4.00);\n", bpm);
    printf("  timing(1,%.2f,4.00);\n", abpm);
    printf("  timing(%d,0.00,4.00);\n", int(t3 + ti + 0.5));
    printf("  timing(%d,-1.00,4.00);\n", int(t3 + ti + n * dt + 0.5));
    printf("  scenecontrol(%d,hidegroup,0.00,1);\n", int(t3 + ti + n * dt + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(t3 + ti + n * dt + 1 + 0.5), bpm);
    //坐标-->实际arc数据 (平动+转动)
    for (int idx = 0; idx < idx_mEnd; idx++) {
        model[idx].x01 = (model[idx].m3.matrix[0][0] + model[idx].m1.matrix[0][0]) * rx + x_0;
        model[idx].y01 = (model[idx].m3.matrix[1][0] + model[idx].m1.matrix[1][0]) + y_0;
        model[idx].t01 = (model[idx].m3.matrix[2][0] + model[idx].m1.matrix[2][0]) * rz + t3 + ti + n * dt + offset / bpm + 0.5;
        model[idx].x02 = (model[idx].m4.matrix[0][0] + model[idx].m2.matrix[0][0]) * rx + x_0;
        model[idx].y02 = (model[idx].m4.matrix[1][0] + model[idx].m2.matrix[1][0]) + y_0;
        model[idx].t02 = (model[idx].m4.matrix[2][0] + model[idx].m2.matrix[2][0]) * rz + t3 + ti + n * dt + offset / bpm + 0.5;
        if (model[idx].t01 > model[idx].t02) {
            swap(model[idx].t01, model[idx].t02);
            swap(model[idx].x01, model[idx].x02);
            swap(model[idx].y01, model[idx].y02);
        }
        printf("  arc(%d,%d,%.2f,%.2f,s,%.2f,%.2f,0,none,true);\n", model[idx].t01, model[idx].t02, model[idx].x01,
               model[idx].x02,
               model[idx].y01, model[idx].y02);
    }
    printf("};\n");
}

//静止帧
void motionless(int t) {
    printf("timinggroup(noinput){\n");
    printf("  timing(0,%.2f,4.00);\n", bpm);
    printf("  timing(%d,50000.00,4.00);\n", int(tt + ti - 999 + 0.5));
    printf("  timing(%d,0.00,4.00);\n", int(tt + ti + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(tt + ti + t + 0.5), abpm);
    printf("  scenecontrol(%d,hidegroup,0.00,1);\n", int(tt + ti + t + 0.5));
    printf("  timing(%d,-1.00,4.00);\n", int(tt + ti + t + 10 + 0.5));
    printf("  timing(%d,0.00,4.00);\n", int(tt + ti + t + 10 + 10000 - offset + 0.5));
    printf("  timing(%d,%.2f,4.00);\n", int(tt + ts - 10000 / bpm - 10 + 0.5), -abpm);
    printf("  timing(%d,%.2f,4.00);\n", int(tt + ts - 10000 / bpm + 0.5), bpm);
    //坐标-->实际arc数据 (平动+转动)
    for (int idx = 0; idx < idx_mEnd; idx++) {
        model[idx].x01 = (model[idx].m3.matrix[0][0] + model[idx].m1.matrix[0][0]) * rx + x_0;
        model[idx].y01 = (model[idx].m3.matrix[1][0] + model[idx].m1.matrix[1][0]) + y_0;
        model[idx].t01 = (model[idx].m3.matrix[2][0] + model[idx].m1.matrix[2][0]) * rz + tt + ts;
        model[idx].x02 = (model[idx].m4.matrix[0][0] + model[idx].m2.matrix[0][0]) * rx + x_0;
        model[idx].y02 = (model[idx].m4.matrix[1][0] + model[idx].m2.matrix[1][0]) + y_0;
        model[idx].t02 = (model[idx].m4.matrix[2][0] + model[idx].m2.matrix[2][0]) * rz + tt + ts;
        if (model[idx].t01 > model[idx].t02) {
            swap(model[idx].t01, model[idx].t02);
            swap(model[idx].x01, model[idx].x02);
            swap(model[idx].y01, model[idx].y02);
        }
        printf("  arc(%d,%d,%.2f,%.2f,s,%.2f,%.2f,0,none,true);\n", model[idx].t01, model[idx].t02, model[idx].x01,
               model[idx].x02,
               model[idx].y01, model[idx].y02);
    }
    printf("};\n");
    tt += t;
}

void printArc(float _t3) {
    switch (ptype) {
        case 1:
            printArc1(_t3);
            break;
        case 2:
            printArc2(_t3);
            break;
    }
}

//平移旋转
void move(float _dx, float _dy, float _dz, float _thx, float _thy, float _thz, int _t) {
    float dx = _dx, dy = _dy, dz = _dz, thx = _thx, thy = _thy, thz = _thz;
    int t = _t;
    //printf("%f %f %f %f %f %f %d\n", dx, dy, dz, thx, thy, thz, t);

    //计算速度
    double vx = dx / t;
    double vy = dy / t;
    double vz = dz / t;

    //计算角速度 ms^-1
    double wx = thx * pi / 180 / (float)t;
    double wy = thy * pi / 180 / (float)t;
    double wz = thz * pi / 180 / (float)t;

    //生成平动变化矩阵
    Matrix trans = InitMatrix(3, 1);
    trans.matrix[0][0] = vx * dt;
    trans.matrix[1][0] = vy * dt;
    trans.matrix[2][0] = vz * dt;

    //生成转动变化矩阵
    Matrix rX = InitMatrix(3, 3);
    Matrix rY = InitMatrix(3, 3);
    Matrix rZ = InitMatrix(3, 3);

    rX.matrix[0][0] = 1, rX.matrix[0][1] = 0, rX.matrix[0][2] = 0;
    rX.matrix[1][0] = 0, rX.matrix[1][1] = cos(wx * dt), rX.matrix[1][2] = sin(wx * dt);
    rX.matrix[2][0] = 0, rX.matrix[2][1] = -sin(wx * dt), rX.matrix[2][2] = cos(wx * dt);

    rY.matrix[0][0] = cos(wy * dt), rY.matrix[0][1] = 0, rY.matrix[0][2] = -sin(wy * dt);
    rY.matrix[1][0] = 0, rY.matrix[1][1] = 1, rY.matrix[1][2] = 0;
    rY.matrix[2][0] = sin(wy * dt), rY.matrix[2][1] = 0, rY.matrix[2][2] = cos(wy * dt);

    rZ.matrix[0][0] = cos(wz * dt), rZ.matrix[0][1] = sin(wz * dt), rZ.matrix[0][2] = 0;
    rZ.matrix[1][0] = -sin(wz * dt), rZ.matrix[1][1] = cos(wz * dt), rZ.matrix[1][2] = 0;
    rZ.matrix[2][0] = 0, rZ.matrix[2][1] = 0, rZ.matrix[2][2] = 1;

    //printMatrix(rY);

    //t3为当前动画已播放时间
    double t3 = 0;

    //变化循环
    while (t3 <= t) {
        printArc(tt + t3);
        for (int i = 0; i < idx_mEnd; i++) {
            model[i].m1 = mul(rZ, mul(rY, mul(rX, model[i].m1)));
            model[i].m2 = mul(rZ, mul(rY, mul(rX, model[i].m2)));
            model[i].m3 = add(model[i].m3, trans);
            model[i].m4 = add(model[i].m4, trans);
        }
        t3 += dt;
    }
    printArc(tt + t3);
    tt += t;
}

int main() {
    //输入初始模型
    int t0;
    printf("%s", "请输入模型出现时间(中心时间)\n");
    scanf("%d", &t0);
    printf("%s", "请输入模型,最后一行仅有一\"#\"\n");
    char _in[616];
    scanf("%s", _in);
    int i;
    for (i = 0; _in[0] != '#'; i++) {
        sscanf(_in, "arc(%d,%d,%f,%f,s,%f,%f,0,none,true);",
               &model[i].t01, &model[i].t02, &model[i].x01, &model[i].x02,
               &model[i].y01, &model[i].y02);

        //计算相对坐标
        model[i].x1 = (model[i].x01 - x_0) / rx;
        model[i].x2 = (model[i].x02 - x_0) / rx;
        model[i].y1 = model[i].y01 - y_0;
        model[i].y2 = model[i].y02 - y_0;
        model[i].z1 = (model[i].t01 - t0) / rz;
        model[i].z2 = (model[i].t02 - t0) / rz;

        //m1,m2存储始末坐标
        model[i].m1 = InitMatrix(3, 1);
        model[i].m2 = InitMatrix(3, 1);
        model[i].m1.matrix[0][0] = model[i].x1;
        model[i].m1.matrix[1][0] = model[i].y1;
        model[i].m1.matrix[2][0] = model[i].z1;
        model[i].m2.matrix[0][0] = model[i].x2;
        model[i].m2.matrix[1][0] = model[i].y2;
        model[i].m2.matrix[2][0] = model[i].z2;

        //m3,m4全部填0
        model[i].m3 = InitMatrix(3, 1);
        model[i].m4 = InitMatrix(3, 1);

        scanf("%s", _in);
    }
    //index for model's end 记录 model 数组中总arc数量 初始值为0
    idx_mEnd = i;

    //相关参数赋值
    printf("动画起始时间(大于或等于1000):\n");
    scanf("%d", &ti);
    printf("动画帧率(参考值30或60):\n");
    scanf("%d", &fps);
    printf("基础BPM(bpm):\n");
    scanf("%f", &bpm);
    printf("动画使用最高BPM(参考值30000):\n");
    scanf("%f", &abpm);
    printf("动画生成方式(参考值1或2):\n");
    scanf("%d", &ptype);

    dt = 1000 / fps;

    //变换
    int type, t;
    float dx_or_t, dy, dz, thx, thy, thz;
    printf("%s",
           "请输入变换操作 可输入若干行 \"#\"视为结束 格式如下\n类型参数,其余参数\n类型0:结束 格式如下\n0\n示例:0\n类型1:平移旋转 格式如下\n1,dx,dy,dz,θx,θy,θz,持续时间\n示例:1,0,100,500,0,360,0,1000\n类型2:静止帧 格式如下\n1,dx,dy,dz,θx,θy,θz,持续时间\n示例:2,1000\n");
    scanf("%d,%f,%f,%f,%f,%f,%f,%d", &type, &dx_or_t, &dy, &dz, &thx, &thy, &thz, &t);
    while (type) {
        switch (type) {
            case 1:
                move(dx_or_t, dy, dz, thx, thy, thz, t);
                break;
            case 2:
                motionless(int(dx_or_t));
                break;
        }
        scanf("%d,%f,%f,%f,%f,%f,%f,%d", &type, &dx_or_t, &dy, &dz, &thx, &thy, &thz, &t);
    }

    return 0;
}

将输入文件写到arc.txt中 大致内容如下

100000
arc(99987,99987,0.31,0.31,s,0.15,0.85,0,none,true);
arc(99987,99987,0.69,0.69,s,0.15,0.85,0,none,true);
arc(99987,99987,0.31,0.69,s,0.15,0.15,0,none,true);
arc(99987,99987,0.31,0.69,s,0.85,0.85,0,none,true);
arc(99987,100012,0.31,0.31,s,0.15,0.15,0,none,true);
arc(99987,100012,0.69,0.69,s,0.85,0.85,0,none,true);
arc(99987,100012,0.31,0.31,s,0.85,0.85,0,none,true);
arc(99987,100012,0.69,0.69,s,0.15,0.15,0,none,true);
arc(100012,100012,0.31,0.31,s,0.15,0.85,0,none,true);
arc(100012,100012,0.69,0.69,s,0.15,0.85,0,none,true);
arc(100012,100012,0.31,0.69,s,0.15,0.15,0,none,true);
arc(100012,100012,0.31,0.69,s,0.85,0.85,0,none,true);
#
1000
60
90
30000
1
1,0,0,0,0,720,0,9000
0

#之后的数据依次为动画开始时间 帧率 基础bpm 动画所用bpm 生成方案 变换语句 最终以0结束
关于变换语句细则请查看注释
编译出arc.exe后用cmd来调用
arc.exe<arc.txt >arc.out
将结果输出到arc.out中方便粘贴

也可直接执行exe文件 不过复制起来有些困难


0x05 效果展示

试图还原d0的BGA


0x06 The End

希望我多年后看到这篇还能想起来我这个程序是干什么用的((

最后修改:2021 年 08 月 08 日 10 : 36 AM
如果觉得我的文章对你有用,请随意赞赏