之前在 Lite2D 里做旋转时,我尝试过用矩阵变换的方式来处理,但实测下来发现自己写出来的效率还不如朴素的三角函数计算加上缓存的组合。

瓶颈主要在于 sincos 的运算成本。为此我设计了一个查表式的缓存:程序启动时预先计算 0 到 90 度范围内的结果,精度控制在 0.000001,运行期直接查表,速度提升非常明显。

下面是 Lite2D 中的具体实现。

float Math::sin(const float& angle)
{
	static const int scale = 100000;
	static const int maxAngle = 360 * scale;
	static const int arraySize = maxAngle / 4;
	static float _sin[arraySize] = { 0 };
	// init sin
	if (!_sin[1])
	{
		if (auto prf = fopen("sin.dat", "rb"))
		{
			fread(&_sin, sizeof(float), arraySize, prf);
			fclose(prf);
		}
		else
		{
			const auto step = 3.1415926535898 / (arraySize * 2);
			auto radian = .0;
			for (int i = 0; i < arraySize; ++i, radian += step)
			{
				_sin[i] = static_cast<float>(std::sin(radian));
			}
			auto t = std::thread([=]
			{
			// write into file
			if (FILE *pwf = fopen("sin.dat", "wb"))
			{
				fwrite(_sin, sizeof(float), arraySize, pwf);
				fclose(pwf);
			}
			});

			t.detach();

		}
	}

	auto realAngle = angle*scale;
	if (realAngle >= maxAngle || realAngle < 0)
	{
		realAngle = int(realAngle + (realAngle >= 0 ? 0.5 : -0.5)) % maxAngle;
		if (realAngle < 0)
			realAngle += 360;
	}

	int index = int(realAngle);
	switch (index / arraySize)
	{
	case 0:
		return _sin[index];
	case 1:
		if (index == arraySize)
			return 1;
		return _sin[2 * arraySize - index];
	case 2:
		return -_sin[index - 2 * arraySize];
	case 3:
		if (index == 3 * arraySize)
			return -1;
		return -_sin[maxAngle - index];
	}
	return 0;
}

float Math::cos(const float& angle)
{
	return sin(angle + 90);
}

其实完全没必要把预计算结果写入文件缓存——磁盘 I/O 的速度远远慢于重新做一遍这种简单的三角函数计算。