почему бы не сделать свои LUT в фиксированной точке? что-то вроде этого в C ++:
const int fp=1000; // fixed point precision
const int mycos[360]={ 1000, 999, 999, 998, 997, 996, 994, 992, 990, ... }
float x,y,x0=0,y0=0,r=50,ang=45;
x = x0 + ( (r*mycos[ ang %360]) / fp );
y = y0 + ( (r*mycos[(ang+90)%360]) / fp );
Также вы можете написать скрипт, который создаст LUT для вас. Каждое значение в LUT вычисляется следующим образом:
LUT[i] = fp*cos(i*M_PI/180); // i = 0,1,2,...359
Теперь для нормализации угла перед использованием:
ang %= 360;
if (ang<0) ang+=360;
Существуют также способы вычисления таблиц sin,cos
с целочисленными переменными только там. Мы использовали его в 8-битной эре asm на Z80 для наших вещей, а затем и для демонстраций x86 ... так что можно написать код, который будет создавать его непосредственно в сценарии minecraft без необходимости использования другого компилятора.
Вы можете даже изменить угловые единицы на степень 2 вместо 360, чтобы вы могли избавиться от модуля, а также установить fp на косилку 2 -1, чтобы вам не нужно было даже делить. После некоторых поисков в моих исходных архивах я обнаружил мою древнюю демоверсию TASM MS-DOS , которая использует эту технику. После его переноса на C ++ и настройки констант здесь C ++ результат:
int mysinLUT[256];
void mysin_init100() // <-100,+100>
{
int bx,si=620,cx=0,dx; // si ~amplitude
for (bx=0;bx<256;bx++)
{
mysinLUT[bx]=(cx>>8);
cx+=si;
dx=41*cx;
if (dx<0) dx=-((-dx)>>16); else dx>>=16;
si-=dx;
}
}
void mysin_init127() // <-127,+127>
{
int bx,si=793,cx=0,dx; // si ~amplitude
for (bx=0;bx<256;bx++)
{
mysinLUT[bx]=(cx>>8)+1;
cx+=si;
dx=41*cx;
if (dx<0) dx=-((-dx)>>16); else dx>>=16;
si-=dx;
}
}
int mysin(int a){ return mysinLUT[(a )&255]; }
int mycos(int a){ return mysinLUT[(a+64)&255]; }
Константы установлены таким образом, что sin[256]
содержит грубое приближение синуса в пределах диапазона <-100,+100>
или <-127,+127>
(зависит от того, какую инициализацию вы вызываете) и период угла равен 256
вместо 360
. Сначала вам нужно вызвать mysin_init???();
один раз, чтобы инициировать LUT , после чего вы можете использовать mysin,mycos
, только не забудьте разделить окончательный результат на /100
или >>7
.
Когда я отрисовываю оверлей реального и приближенного круга, используя VCL :
void draw()
{
// select range
// #define range100
#define range127
// init sin LUT just once
static bool _init=true;
if (_init)
{
_init=false;
#ifdef range100
mysin_init100();
#endif
#ifdef range127
mysin_init127();
#endif
}
int a,x,y,x0,y0,r;
// clear screen
bmp->Canvas->Brush->Color=clWhite;
bmp->Canvas->FillRect(TRect(0,0,xs,ys));
// compute circle size from window resolution xs,ys
x0=xs>>1;
y0=ys>>1;
r=x0; if (r>y0) r=y0; r=(r*7)/10;
// render real circle
bmp->Canvas->Pen->Color=clRed;
bmp->Canvas->Ellipse(x0-r,y0-r,x0+r,y0+r);
// render approximated circle
bmp->Canvas->Pen->Color=clBlack;
for (a=0;a<=256;a++)
{
#ifdef range100
x=x0+((r*mycos(a))/100);
y=y0-((r*mysin(a))/100);
#endif
#ifdef range127
// if >> is signed (copying MSB)
x=x0+((r*mycos(a))>>7);
y=y0-((r*mysin(a))>>7);
// if >> is unsigned (inserting 0) and all circle points are non negative
// x=( (x0<<7)+(r*mycos(a)) )>>7;
// y=( (y0<<7)-(r*mysin(a)) )>>7;
// this should work no matter what
// x=r*mycos(a); if (x<0) x=-((-x)>>7); else x>>=7; x=x0+x;
// y=r*mysin(a); if (y<0) y=-((-y)>>7); else y>>=7; y=y0-y;
// this work no matter what but use signed division
// x=x0+((r*mycos(a))/127);
// y=y0-((r*mysin(a))/127);
#endif
if (!a) bmp->Canvas->MoveTo(x,y);
else bmp->Canvas->LineTo(x,y);
}
Form1->Canvas->Draw(0,0,bmp);
//bmp->SaveToFile("out.bmp");
}
результат выглядит так:
![result](https://i.stack.imgur.com/m8wqB.png)
Красный - реальный круг, а Черный - круг с использованием mysin,mycos
. Как вы можете видеть, есть небольшие отклонения из-за точности приближений, но здесь не используется операция с плавающей точкой. Это странно, так как три метода смещения битов приводят к разным числам (это должна быть некоторая оптимизация моего компилятора), константы настроены для первого метода.