аналитический подход - не единственный способ атаковать это (не подходит для этого сайта в любом случае), поэтому есть немало других вариантов. Но вы не указали, как должна выглядеть спираль, поскольку есть 3 конца формы, а спираль имеет только 2 ...
Для простоты я предполагаю спиральную оболочку (как вы обматываете веревку вокруг фигуры).
Так как на это напасть? Обычный подход заключается в создании отображения 2D <--> 3D
между некоторой прямоугольной 2D-областью (текстурой) и вашей поверхностью. Обычно используется топология формы, например, цилиндрические / сферические координаты и т. Д.
Другой вариант - нарезать вашу фигуру на отдельные кусочки и обрабатывать каждый кусочек как 2D ... Что значительно упрощает процесс.
Чтобы применить спираль к некоторой изогнутой поверхности, вы либо проецируете ее, либо находите пересечение между формой и направлением к фактической точке на спирали, определенной параметрическим уравнением.
Вот пример C ++ с использованием визуализации OpenGL:
//---------------------------------------------------------------------------
List<int> slice; // start index of each z slice in pnt
List<double> pnt; // GL_POINTS surface points
List<int> lin; // LINE_STRIP spiral point indexes
//---------------------------------------------------------------------------
void obj_init()
{
const double ry=sqrt(1.0/3.0);
const double rx=1.0+ry;
double a,x,y,z,d,N=7.0,da,dx,dy,dz,r,rr,_zero;
int i,j,i0,i1,ii;
// get points of analytical surface
pnt.num=0;
slice.num=0;
d=0.02; dz=0.25*d;_zero=1e-2;
for (z=0.0;z<=1.0;z+=dz,slice.add(pnt.num))
for (x=-rx;x<=rx;x+=d)
for (y=-ry;y<=ry;y+=d)
if (fabs(((1.0-z)*(((x-1.0)*(x-1.0))+y*y-(1.0/3.0))*(((x+1.0)*(x+1.0))+(y*y)-(1.0/3.0)))+(z*((x*x)+(y*y)-(1.0/3.0))))<=_zero)
{
pnt.add(x);
pnt.add(y);
pnt.add(4.0*z-2.0);
}
// spiral line
lin.num=0;
da=5.0*M_PI/180.0;
d=pnt[slice[1]+2]-pnt[slice[0]+2];
for (a=0.0;a<=2.0*M_PI*N;a+=da) // go through whole spiral
{
z=a/(2.0*M_PI*N); // z = f(angle,screws)
z=4.0*z-2.0;
j=((z+2.0)/d);
i0=j-1; if (i0<0) i0=0; if (i0>=slice.num) break;
i1=j+1; if (i1<0) continue; if (i1>=slice.num) i1=slice.num-1;
i0=slice.dat[i0];
i1=slice.dat[i1];
dx=cos(a); // direction vector to the spiral point
dy=sin(a);
dz=z;
for (rr=0.0,i=i0;i<i1;i+=3) // find point with biggest distance from center and close to a,dx,dy
{
x=pnt.dat[i+0];
y=pnt.dat[i+1];
r=(x*dx)+(y*dy)+(z*dz); // dot( (dx,dy,dz) , (x,y,z) )
if (r>rr){ii=i; rr=r; } // better point found
}
if (ii>=0) lin.add(ii); // add spiral point to lin[]
}
}
//---------------------------------------------------------------------------
void obj_draw()
{
int i,j;
glColor3f(0.3,0.3,0.3);
glBegin(GL_POINTS);
for (i=0;i<pnt.num;i+=3) glVertex3dv(pnt.dat+i);
glEnd();
glColor3f(0.2,0.9,0.2);
glBegin(GL_LINE_STRIP);
for (i=0;i<lin.num;i+=3) glVertex3dv(pnt.dat+lin.dat[i]);
glEnd();
}
//---------------------------------------------------------------------------
и предварительный просмотр:
![preview](https://i.stack.imgur.com/WjQmZ.png)
То, что я сделал, просто:
получить точки поверхности pnt[]
просто протестируйте все (x,y,z)
точки в некотором BBOX объеме, и если результат уравнения для точки близок к нулю, я добавлю его в список точек pnt[]
. Благодаря тому, что я использую вложенные циклы for с внешней осью z
, точки уже отсортированы по координате z
, а список slice[]
содержит начальные индексы точек для каждого среза z
, поэтому нет необходимости медленный поиск по всему списку, поскольку я могу перейти от z
к индексу срезов напрямую.
вычислить параметрическую спираль
Используя параметрическое уравнение цилиндра, я могу вычислить x,y,z
для любого t=<0,1>
Если я знаю радиус (1.0
) и количество винтов (N
). Поскольку z
уже является параметром фигуры, я могу использовать его вместо t
...
вычисление пересечения между спиралью и формой
просто перебрать всю спираль. Для каждой из ее точек найдите точку, которая имеет одинаковое направление от центральной оси спирали / формы и находится дальше ... Это можно сделать простым произведением точек между векторами направления. Так что просто используя z
координату фактической тестируемой точки на спиральном вычислении среза фигуры и протестируйте все ее точки, помня максимальное значение точечного произведения. Поскольку центральная ось равна z
, суперпозиции не нужны ... Индексы найденных точек сохраняются в lin[]
для последующего использования ...
Как вы можете видеть на превью, спираль немного зазубренна. Это связано с нелинейным распределением точек. Если вы создаете топологию точек на каждом срезе и соединяете все соседние срезы, вы можете интерполировать любую точку поверхности, устраняя эту проблему. После этого вы можете использовать гораздо меньше точек с еще лучшим результатом, а также визуализировать грани вместо просто каркасной.
Другой вариант - просто сгладить точки спирали, используя усредняющий FIR-фильтр
Остерегайтесь кода, который я изменил масштаб оси z
(z' = 4*z - 2
), чтобы он соответствовал сюжету в вашей ссылке ...
[Edit1] спираль с выравниванием по траектории
Вы можете создать кривую / полилинию или что-либо еще, описывающее центр спирали / спирали. Мне было лень строить такие контрольные точки, поэтому вместо этого я использовал центральные точки фигуры (вычисленные как 0.5*(min+max)
из x,y
координаты для каждого среза, но только для половины формы (x<0.0
) для частей, где "брюки") 2 трубки ... на срез. Остальное я просто интерполировал по квадратичной кривой ...
Из этого просто обработайте все центры спирали / спирали, вычислите локальную матрицу TBN (касательную, нормальную, бинормальную), где касательная также является касательной к траектории центров, а одна из осей совмещена с осью Y
, так как без изменений (y=0
), поэтому угол спирали будет выровнен по одной оси и не скручен. Из этого просто вычислите угол спирали (длина центральной линии от начала пути является параметром) и примените цилиндрические координаты к t,b,n
базисным векторам, а не непосредственно к x,y,z
координатам.
После этого просто направьте луч из центра в направлении спирали, а при попадании на пересечение с поверхностью визуализируйте / сохраните точку ...
Вот предварительный просмотр результата:
![path aligned spiral](https://i.stack.imgur.com/wIhxc.png)
И обновленный код C ++ / VCL / GL:
//---------------------------------------------------------------------------
List<int> slice; // start index of each z slice in pnt
List<double> pnt; // GL_POINTS surface points
List<double> path; // LINE_STRIP curved helix center points
List<double> spir; // LINE_STRIP spiral points
//---------------------------------------------------------------------------
void obj_init()
{
const double ry=sqrt(1.0/3.0);
const double rx=1.0+ry;
double a,x,y,z,zz,d,N=12.0,da,dx,dy,dz,ex,ey,ez,r,rr,_zero;
int i,j,i0,i1,ii;
// [get points of analytical surface]
pnt.num=0;
slice.num=0;
d=0.02; dz=0.25*d;_zero=1e-2;
for (z=0.0;z<=1.0;z+=dz,slice.add(pnt.num))
for (x=-rx;x<=rx;x+=d)
for (y=-ry;y<=ry;y+=d)
if (fabs(((1.0-z)*(((x-1.0)*(x-1.0))+y*y-(1.0/3.0))*(((x+1.0)*(x+1.0))+(y*y)-(1.0/3.0)))+(z*((x*x)+(y*y)-(1.0/3.0))))<=_zero)
{
pnt.add(x);
pnt.add(y);
pnt.add(4.0*z-2.0);
}
// [helix center path] as center point of half-slice
path.num=0;
for (i1=0,j=0;j<slice.num;j++) // all slices
{
i0=i1; i1=slice.dat[j];
dx=+9; ex=-9;
dy=+9; ey=-9;
dz=+9; ez=-9;
for (i=i0;i<i1;i+=3) // single slice
{
x=pnt.dat[i+0];
y=pnt.dat[i+1];
z=pnt.dat[i+2];
if (x<=0.0) // just left side of pants
{
if (dx>x) dx=x; if (ex<x) ex=x; // min,max
if (dy>y) dy=y; if (ey<y) ey=y;
if (dz>z) dz=z; if (ez<z) ez=z;
}
}
if (dz>0.25) break; // stop before pants join
path.add(0.5*(dx+ex));
path.add(0.5*(dy+ey));
path.add(0.5*(dz+ez));
}
// smooth by averaging
for (j=0;j<20;j++)
for (i=3;i<path.num;i+=3)
{
path.dat[i+0]=0.75*path.dat[i+0]+0.25*path.dat[i-3+0];
path.dat[i+1]=0.75*path.dat[i+1]+0.25*path.dat[i-3+1];
path.dat[i+2]=0.75*path.dat[i+2]+0.25*path.dat[i-3+2];
}
// interpolate bridge between pants from last path to (0.0,0.0,0.75)
i=path.num-3;
dx=path.dat[i+0];
dy=path.dat[i+1];
dz=path.dat[i+2];
for (a=0.0;a<1.0;a+=d)
{
x=dx*(1.0-a*a);
y=dy;
z=dz+0.5*a;
path.add(x);
path.add(y);
path.add(z);
}
// mirror/reverse other half
for (i=path.num-3;i>=0;i-=3)
{
path.add(-path.dat[i+0]);
path.add( path.dat[i+1]);
path.add( path.dat[i+2]);
}
// [path aligned spiral line envelope]
spir.num=0; _zero=1e-2; d=0.01;
double *p1,*p0,t[3],b[3],n[3]; // spiral center (actual,previous), TBN (tangent,binormal,normal,tangent)
double u[3],v[3],p[3],dp[3];
vector_sub(p,path.dat+3,path.dat); // mirro first path point
vector_sub(p,path.dat,p);
p1=p;
for (j=0;j<path.num;j+=3) // go through whole path
{
// path center previous,actual
p0=p1;
p1=path.dat+j;
// TBN basis vectors of the spiral slice
vector_sub(t,p1,p0); vector_one(t,t); // tangent direction to next center o npath
vector_ld(n,0.0,1.0,0.0);
vector_mul(b,n,t); vector_one(b,b); // binormal perpendicular to Y axis (path does not change in Y) and tangent
vector_mul(n,t,b); vector_one(n,n); // normal perpendiculer to tangent and binormal
// angle from j as parameter and screws N
a=N*2.0*M_PI*double(j)/double(path.num-3);
// dp = direction to spiral point
vector_mul(u,n,sin(a));
vector_mul(v,b,cos(a));
vector_add(dp,u,v);
vector_mul(dp,dp,d);
// center, step
x=p1[0]; dx=dp[0];
y=p1[1]; dy=dp[1];
z=p1[2]; dz=dp[2];
// find intersection between p+t*dp and surface (ray casting)
for (r=0.0;r<2.0;r+=d,x+=dx,y+=dy,z+=dz)
{
zz=(z+2.0)*0.25;
if (fabs(((1.0-zz)*(((x-1.0)*(x-1.0))+y*y-(1.0/3.0))*(((x+1.0)*(x+1.0))+(y*y)-(1.0/3.0)))+(zz*((x*x)+(y*y)-(1.0/3.0))))<=_zero)
{
spir.add(x);
spir.add(y);
spir.add(z);
break;
}
}
}
}
//---------------------------------------------------------------------------
void obj_draw()
{
int i,j;
glColor3f(0.3,0.3,0.3);
glBegin(GL_POINTS);
for (i=0;i<pnt.num;i+=3) glVertex3dv(pnt.dat+i);
glEnd();
glLineWidth(5.0);
glColor3f(0.0,0.0,0.9);
glBegin(GL_LINE_STRIP);
for (i=0;i<path.num;i+=3) glVertex3dv(path.dat+i);
glEnd();
glColor3f(0.9,0.0,0.0);
glBegin(GL_LINE_STRIP);
for (i=0;i<spir.num;i+=3) glVertex3dv(spir.dat+i);
glEnd();
glLineWidth(1.0);
}
//---------------------------------------------------------------------------
Я также сгладил путь и вычислил только половину его ... так как остальное - просто копирование / зеркало ...
Этот подход должен работать для любой аналитической поверхности и траектории центра ...
Я также использую мой шаблон динамического списка так:
List<double> xxx;
совпадает с double xxx[];
xxx.add(5);
добавляет 5
в конец списка
xxx[7]
элемент массива доступа (безопасный)
xxx.dat[7]
элемент массива доступа (небезопасный, но быстрый прямой доступ)
xxx.num
- фактический используемый размер массива
xxx.reset()
очищает массив и устанавливает xxx.num=0
xxx.allocate(100)
предварительно выделить место для 100
элементов
Некоторые связанные показатели качества: