Рисование эпициклонов

22

Эпициклоида является кривой точка на окружности , как это делает катается еще один круг. Cyclogon это форма точка на правильный многоугольник делает , как она катится по плоскости. Epicyclogon это кривая , описываемая точкой на одном правильного многоугольника , как она катится вокруг другого.

Написать программу , которая рисует epicyclogon данное r, r1, r2, n1, n2:

r = number of clockwise revolutions rolling polygon makes around stationary polygon (any real number as limited by float values) 
r1 = distance from center of stationary polygon to each of its vertices (positive real number)
r2 = distance from center of rolling polygon to each of its vertices (positive real number)
n1 = number of sides stationary polygon has (integer greater than 2)
n2 = number of sides rolling polygon has (integer greater than 2)

Заметки

  • При rотрицательном положении ролик должен идти против часовой стрелки .
  • Ибо rодин оборот происходит, когда линия, соединяющая центроиды двух фигур, выметает все 360 градусов. Это понятие расширено, чтобы включить все значения r. (Таким образом, за четверть оборота линия, соединяющая центроиды, выметается на 90 градусов.)
  • Эти аргументы должны исходить из командной строки или ваша программа должна запрашивать их (например, с помощью Python input()).
  • r1и r2относятся друг к другу, а не размеры изображения. Таким образом, вы можете установить одну «единицу измерения» на любое количество реальных пикселей.

Точка, которую вы должны отследить, является одной из вершин формы вращения. Формы должны начинаться с этой вершины, касающейся стационарной вершины и двух соседних сторон:

пример эпициклона

Точные начальные вершины и угол неподвижного многоугольника не имеют значения.

Выход

Вывод должен идти к изображению размером не менее 600x600 пикселей (или с некоторым изменяемым размером, который может быть установлен на 600). Он должен показывать всю кривую эпициклона, заданную параметрами, хорошо оформленными на изображении.

Скользящие и стационарные многоугольники также должны быть нарисованы (с валиком в его окончательном состоянии). Две фигуры и эпициклон должны быть трех заметно разных цветов.

Там также должен быть простой способ не рисовать многоугольники (смена trueна falseв коде хватает).

Пожалуйста, покажите нам как минимум 2 выходных изображения. Это нормально, чтобы уменьшить их, если это необходимо.

счет

Самый короткий код, который производит действительные выходные изображения, побеждает.

Бонусы

  • Минус 50 байтов, если выводом является анимированный GIF (или аналогичный) нарисованной кривой.
  • Минус 150 байт, если вы позволите n1и n2примете значение 2, чтобы фигуры стали отрезками длины 2 * r1(или r2), «катаясь» друг вокруг друга. Как вы справляетесь, rкогда n1и когда n22, зависит от вас, поскольку центроиды не вращаются вокруг друг друга, как в других случаях. (Не «катиться» вообще не считается обработкой.)

Так как мне не терпится увидеть, как эта новая идея выполнена хорошо (и это не совсем легкая прогулка), я собираюсь наградить победителя 150 наградами . Конкурс завершится в тот же день, когда заканчивается награда.

Награда не будет присуждена победителю, если станет ясно, что они просто переписали большую часть кода из другой заявки.

Библиотечные функции, которые уже делают это (если они есть), не допускаются.

Примечание. Это связано с моими оставшимися вопросами, которые каждый может свободно публиковать. Но если никто не публикует их, есть хороший шанс, что я вовремя. :П

Кальвин Хобби
источник
Я думаю, что против часовой стрелки должен быть положительным вместо.
Сохам Чоудхури
3
@ SohamChowdhury Я думаю, это вряд ли имеет значение.
Увлечения Кельвина
Вы правы, на самом деле. У вас есть примеры изображений? У меня нет проигрывателя CDF.
Сохам Чоудхури
@ githubphagocyte Я понимаю вашу точку зрения. Исправлена.
Увлечения Кэлвина
@ MartinBüttner Не слишком строго, это было только первое, что я думал. При необходимости вы можете запросить значения другим способом.
Увлечения Кэлвина

Ответы:

3

MATLAB: 735 байт - бонус 200 = 535

Моя программа обрабатывает случай n = 2 и рисует анимацию в реальном времени. Есть несколько различий между версиями для гольфа и без игры в гольф:

В версии без игры есть возможность сохранить анимацию в файл «g.gif», установив savegif = 1в коде. По умолчанию он отключен, так как это может раздражать по нескольким причинам:

  • Создание нежелательного файла
  • Возможное отставание
  • Если у вас есть несколько мониторов, а окно графика не отображается правильно, возникает ошибка ... Сохранение картинки в отбрасываемой версии необходимо было сбросить, поскольку оно занимало около 100 байт, что превышало размер бонуса.

Версия без гольфа рисует круг на вершине трассера. Он также производит больше кадров и движется быстрее (хотя это можно изменить в версии для гольфа, изменив числа).

Образцы:

f(11,5,90,2,99,0) после завершения программы

образец гольфа

epic(1.3,4,2,6,6,1) с выходом GIF

негольфированный образец

Код без правил

%epicyclogon animation outputs to 'g.gif' if savegif=1 as well as animating in real time

function[] = epic(r,r1,r2,n1,n2,dispPoly)

savegif = 0;  %set to 1 to write .gif

cs = @(a) [cos(a);sin(a)];
vert = @(r, n, v) r * cs(2*pi*v/n);
polyPt = @(l, s, n, r) vert(r, n, floor(l/s)) + mod(l/s,1)*(vert(r, n, floor(l/s)+1) - vert(r, n, floor(l/s)));
polyPt2 = @(i, f, n, r) vert(r, n, i) + f*(vert(r, n, i+1) - vert(r, n, i));
rotm = @(a) [cos(a) -sin(a);sin(a) cos(a)];
arrpluspt = @(a, p) a + kron(p, ones(1,length(a)));
arg = @(p) atan2(p(2), p(1));

E = 1e-9;

dispPoly = dispPoly / dispPoly;

sgn = sign(-r);
r = abs(r);

s1 = 2*r1*sin(pi/n1);
s2 = 2*r2*sin(pi/n2);

%d1 = (r1*r1 - s1*s1*.25)^.5;
d2 = (r2*r2 - s2*s2*.25)^.5;

plotmax = r1+2*r2;

astep = .05; %determines amount of frames per rotation
delay = .01; % time per frame

l = 0;

lRem = 0;
lr = 0;

P1 = vert(r1, n1, 1:n1+1) * dispPoly; 
trace = [];

first = 1;
while 1

    if lr %exists while rotating about a corner of the stationary
        rotA = 2*pi/n1;
    else
        rotA = 2*pi/n2;
    end
    rotPt = polyPt(l, s1, n1, r1);
    lb = l + lRem;
    side1 = floor(l / s1 - E);
    side1up = side1 + lr;
    p2cen = polyPt2(side1, lb/s1 -side1 - .5 * s2/s1, n1, r1) + d2 * cs(2*pi*(side1+.5)/n1);
    if first
        p2cen0 = p2cen;
        r = r + arg(p2cen0)/(2*pi);
    end

    for a = 0:astep:rotA    
        P2 = vert(r2, n2, 0:n2);
        P2 = rotm( pi +pi/n1 -pi/n2   +2*pi*side1/n1) * P2;
        P2 = arrpluspt(P2, p2cen);
        P2 = arrpluspt(P2, -rotPt);
        P2 = rotm(a) * P2;
        P2 = arrpluspt(P2, rotPt);
        trV = mod(floor(l/s2 + E) + lr, n2) + 1;

        cen = rotm(a) * (p2cen - rotPt) + rotPt;
        trace = [trace,P2(:,trV)]; 

        plot(P1(1,:), sgn*P1(2,:), P2(1,:)*dispPoly, sgn*P2(2,:)*dispPoly, trace(1,:),sgn*trace(2,:),P2(1,trV), sgn*P2(2,trV),'o');

        %plot(P1(1,:), P1(2,:), P2(1,:), P2(2,:), trace(1,:),trace(2,:),...
        %[0,p2cen0(1)],[0,p2cen0(2)],[0,cen(1)],[0,cen(2)], P2(1,trV), P2(2,trV),'o');

        axis([-plotmax,plotmax,-plotmax,plotmax]);
        axis square
        figure(1);
       if savegif
           drawnow
           frame = getframe(1); % plot window must be on same monitor!
           img = frame2im(frame);
           [img1,img2] = rgb2ind(img,256);
       end
       if first
           if savegif
               imwrite(img1,img2,'g','gif','DelayTime',2*delay); %control animation speed(but not really)
           end
           first = 0;
       else
           if savegif
               imwrite(img1,img2,'g','gif','WriteMode','append','DelayTime', 2*delay);
           end
       end
       pause(.01);

        adf = mod(arg(cen) - r*2*pi, 2*pi);
        if adf < astep & l/(n1*s1) + .5 > r
            return
        end

    end

%cleanup for next iteration 
    jump = lRem + ~lr * s2; 
    lnex = l + jump; 

    if floor(lnex / s1 - E) > side1up 
        lnex = s1*(side1up+1);
        lRem = jump - (lnex - l);
        lr = 1;
    else    
        lRem = 0;
        lr = 0;
    end
    l = lnex;
end

Гольф-код

function[]=f(r,h,H,n,N,d)
P=pi;T=2*P;F=@floor;C=@(a)[cos(a);sin(a)];g=@(i,f,n,r)r*C(T*i/n)*(1-f)+f*r*C(T*(i+1)/n);R=@(a)[C(a),C(a+P/2)];W=@(a,p)[a(1,:)+p(1);a(2,:)+p(2)];b=@(p)atan2(p(2),p(1));E=1e-9;d=d/d;S=1-2*(r>0);r=-r*S;x=2*h*sin(P/n);X=2*H*sin(P/N);M=h+2*H;l=0;z=0;L=0;A=h*C(T*(0:n)/n)*d;t=[];while 1
v=l/x;D=F(v-E);q=g(D,v-D,n,h);Z=D+L;c=g(D,v+z/x-D-.5*X/x,n,h)+H*cos(P/N)*C(T*D/n+P/n);r=r+~(l+L)*b(c)/T;for a=0:.1:T/(L*n+~L*N)
O=@(p)W(R(a)*W(p,-q),q);B=O(W(R(P+P/n-P/N+T*D/n)*H*C(T*(0:N)/N),c));t=[t,B(:,mod(F(l/X+E)+L,N)+1)];plot(A(1,:),S*A(2,:),d*B(1,:),d*S*B(2,:),t(1,:),t(2,:)*S)
axis([-M,M,-M,M],'square');pause(.1);if.1>mod(b(O(c))-r*T,T)&v/n+.5>r
return;end;end;j=z+~L*X;J=l+j;L=F(J/x-E)>Z;l=L*x*(Z+1)+~L*J;z=L*(J-l);end

Инструкции:

Сохраните функцию в файл с тем же именем, т.е. epic.mили f.m. Запустите его, вызвав функцию из консоли Matlab.

Использование: epic(r, r1, r2, n1, n2, dispPoly) где dispPoly- логическая переменная (ноль, если ложь, ненулевое число, если истина), определяющая, следует ли рисовать многоугольники.

Изменить: Добавлен бонус 50 за анимированные изображения.

feersum
источник
14

Java - 2726 2634 - 200 = 2434 символа

Улучшено с 3800 байтов

Спасибо всем за ваши предложения (особенно псевдоним117), вот новая версия.

Я добавил класс P, который является точечным классом, и класс L, который расширяет ArrayList

Я также добавил некоторые незначительные логические изменения.

Вот основной класс (не гольф):

import java.awt.*;
import java.awt.geom.*;

import javax.swing.*;
public class Polygons2 extends JPanel{
    public static void main(String[] args) throws InterruptedException{new Polygons2(args);}
    double q=Math.PI*2;
    int d=1;
    public Polygons2(String[] args) throws InterruptedException{
        double revolutions=Double.valueOf(args[0])*q;
        double stationaryRadius = Double.valueOf(args[1]);
        double rollingRadius = Double.valueOf(args[2]);
        int stationarySides = Integer.valueOf(args[3]);
        int rollingSides = Integer.valueOf(args[4]);    
        double dist = stationaryRadius+rollingRadius+70;
        P sp = new P(dist,dist);
        P rp = new P(sp.x,sp.y-rollingRadius-stationaryRadius);
        //get points for rolling polygon and stationary polygon
        int key=0;
        for(double stationaryAngle=-q/4;stationaryAngle<q-q/4;stationaryAngle+=q/stationarySides){
            P p=new P(Math.cos(stationaryAngle)*stationaryRadius+sp.x,Math.sin(stationaryAngle)*stationaryRadius+sp.y);
            p.k=key;key++;
            stationaryPoints.add(p);
        }
        for(double rollingAngle=q/4;rollingAngle<q+q/4;rollingAngle+=q/rollingSides){
            P p=new P(Math.cos(rollingAngle)*rollingRadius+rp.x,Math.sin(rollingAngle)*rollingRadius + rp.y);
            p.k=key;key++;
            rollingPoints.add(p);
        }
        double g=(q/2)-((q/2-(q/rollingSides))/2) - ((q/2-(q/stationarySides))/2)-.05;
        for(P p:rollingPoints){p.r(getPoint(0), g);}
        //set up JFrame
        JFrame f = new JFrame();
        f.add(this);
        f.setSize((int)dist*2+60,(int)dist*2+60);
        f.setVisible(true);
        int[] pKeys= new int[]{stationaryPoints.get(0).k,rollingPoints.get(0).k};
        int index=1;
        P rc = rollingPoints.c();
        P sc =stationaryPoints.c();
        double currentRadian=Math.atan2(rc.y-sc.y,rc.x-sc.x);
        double totalRadian = 0;
        while(Math.abs(totalRadian)<revolutions){
            P rc2 = rollingPoints.c();
            P sc2 =stationaryPoints.c();
            double angle = Math.atan2(rc2.y-sc2.y,rc2.x-sc2.x);
            if(currentRadian-angle<2){totalRadian+=(angle-currentRadian);}
            currentRadian=angle;
            L clone=(L)path.clone();
            clone.add(new P(rollingPoints.get(1).x,rollingPoints.get(1).y));
            path = clone;
            for(P p:rollingPoints){
                p.r(getPoint(pKeys[index]),.01);
                int size = stationaryPoints.size();
                for(int i=0;i<size;i++){
                    P stationaryPointAtI = stationaryPoints.get(i);
                    P nextPoint=null;
                    if(i==size-1){nextPoint=stationaryPoints.get(0);}
                    else{nextPoint=stationaryPoints.get(i+1);}
                    if(p.b(stationaryPointAtI, nextPoint)==1&&containsKey(pKeys,p.k)==0){
                        //rolling point is between 2 stationary points
                        if(index==1){index=0;}else{index=1;}
                        pKeys[index]=p.k;
                    }
                    int size2=rollingPoints.size();
                    for(int h=0;h<size2;h++){
                        P nextPoint2=null;
                        if(h==size2-1){nextPoint2=rollingPoints.get(0);}
                        else{nextPoint2=rollingPoints.get(h+1);}
                        if(stationaryPointAtI.b(rollingPoints.get(h), nextPoint2)==1&&containsKey(pKeys,stationaryPointAtI.k)==0){
                            //stationary point is between 2 rolling points
                            if(index==1){index=0;}else{index=1;}
                            pKeys[index]=stationaryPointAtI.k;
                        }
                    }
                }
            }
            repaint();
            Thread.sleep(5);
        }
    }
    volatile L path = new L();
    L rollingPoints = new L();
    L stationaryPoints = new L();
    P getPoint(int key){
        for(P p:rollingPoints){if(p.k==key){return p;}}
        for(P p:stationaryPoints){if(p.k==key){return p;}}
        return null;
    }
    int containsKey(int[] keys,int key){
        for(int i:keys){if(key==i){return 1;}}
        return 0;
    }
    @Override
    public void paintComponent(Graphics g){
        Path2D.Double sPath = new Path2D.Double();
        sPath.moveTo(stationaryPoints.get(0).x, stationaryPoints.get(0).y);
        for(P p:stationaryPoints){
            sPath.lineTo(p.x, p.y);
        }
        sPath.closePath();
        Path2D.Double rPath = new Path2D.Double();
        rPath.moveTo(rollingPoints.get(0).x, rollingPoints.get(0).y);
        for(P p:rollingPoints){
            rPath.lineTo(p.x, p.y);
        }
        rPath.closePath();
        g.setColor(Color.white);
        g.fillRect(0,0,getWidth(),getHeight());
        Graphics2D t = (Graphics2D)g;
        if(d==1){
        t.setColor(Color.black);
        t.draw(sPath);
        t.setColor(Color.blue);
        t.draw(rPath);
        }
        g.setColor(Color.green);
        for(P p:path){g.fillOval((int)p.x-1, (int)p.y-1, 2, 2);}
    }
}

И версия для гольфа:

import java.awt.*;import java.awt.geom.*;import javax.swing.*;import static java.lang.Math.*;class Polygons2Golfed extends JPanel{public static void main(String[]a)throws Exception{new Polygons2Golfed(a);}double q=PI*2;int d=1;public Polygons2Golfed(String[]a)throws Exception{double b,c,f;b=Double.valueOf(a[1]);c=Double.valueOf(a[2]);int d,e;d=Integer.valueOf(a[3]);e=Integer.valueOf(a[4]);f=b+c+100;P o=new P(f,f);P r=new P(o.x,o.y-c-b);int s=0;for(double u=-q/4;u<q-q/4;u+=q/d){P p=new P(cos(u)*b+o.x,sin(u)*b+o.y);p.k=s;s++;l.add(p);}for(double u=q/4;u<q+q/4;u+=q/e){P p=new P(cos(u)*c+r.x,sin(u)*c+r.y);p.k=s;s++;k.add(p);}double g=q/e/2+q/d/2-.05;for(P p:k){p.r(v(0),g);}JFrame j=new JFrame();j.add(this);j.setSize((int)f*2+60,(int)f*2+60);j.setVisible(true);m=new int[]{l.get(0).k,k.get(0).k};int ad=1;P rc=k.c();P sc=l.c();double ab,ac;ab=atan2(rc.y-sc.y,rc.x-sc.x);ac=0;while(abs(ac)<Double.valueOf(a[0])*q){P rc2=k.c();P sc2=l.c();double ah=atan2(rc2.y-sc2.y,rc2.x-sc2.x);if(ab-ah<2)ac+=(ah-ab);ab=ah;L ag=(L)n.clone();ag.add(new P(k.get(1).x,k.get(1).y));n=ag;for(P p:k){p.r(v(m[ad]),.01);int af=l.size();for(int i=0;i<af;i++){P aa=l.get(i);P w=null;if(i==af-1){w=l.get(0);}else{w=l.get(i+1);}if(p.b(aa, w)==1&&w(p.k)==0){if(ad==1)ad=0;else ad=1;m[ad]=p.k;}int ae=k.size();for(int h=0;h<ae;h++){P u=null;if(h==ae-1)u=k.get(0);else u=k.get(h+1);if(aa.b(k.get(h),u)==1&&w(aa.k)==0){if(ad==1)ad=0;else ad=1;m[ad]=aa.k;}}}}repaint();Thread.sleep(5);}}L n=new L();L k=new L();L l=new L();P v(int key){for(P p:k){if(p.k==key)return p;}for(P p:l){if(p.k==key)return p;}return null;}int[]m;int w(int key){for(int i:m){if(key==i)return 1;}return 0;}@Override public void paintComponent(Graphics g){Path2D.Double aq=new Path2D.Double();aq.moveTo(l.get(0).x,l.get(0).y);for(P p:l){aq.lineTo(p.x, p.y);}aq.closePath();Path2D.Double aw=new Path2D.Double();aw.moveTo(k.get(0).x, k.get(0).y);for(P p:k){aw.lineTo(p.x, p.y);}aw.closePath();g.setColor(Color.white);g.fillRect(0,0,getWidth(),getHeight());Graphics2D t=(Graphics2D)g;if(d==1){t.setColor(Color.black);t.draw(aq);t.setColor(Color.blue);t.draw(aw);}g.setColor(Color.green);for(P p:n){g.fillOval((int)p.x-1,(int)p.y-1,2,2);}}}

Как и классы P:

import java.awt.geom.*;class P{double x,y;public P(double a,double b){x=a;y=b;}int k;void r(P c,double g){double a,r;a=Math.atan2(y-c.y,x-c.x)+g;r=Math.sqrt((c.x-x)*(c.x-x)+(c.y-y)*(c.y-y));x=Math.cos(a)*r+c.x;y=Math.sin(a)*r+c.y;}public int b(P a,P b){if(Line2D.ptSegDist(a.x,a.y,b.x,b.y,x,y)<.5)return 1;return 0;}}

И я:

import java.util.*;public class L extends ArrayList<P>{public P c(){double x,y;x=0;y=0;for(P p:this){x+=p.x;y+=p.y;}return new P(x/size(),y/size());}}

Измените int d на 0 или 1, чтобы показать полигоны

аргументы - 1 100 50 5 2

введите описание изображения здесь

args - 1,5 100 100 7 3

введите описание изображения здесь

args - 2 40 100 3 7

введите описание изображения здесь

Стрейч маньяк
источник
Действительно rли 50 во всех ваших примерах? Это означало бы, что ролик проходит около 50 раз.
Увлечения Кэлвина
@ Calvin'sHobbies новый пример показывает пи * 3
Stretch Maniac
1
@StretchManiac Это не может быть правдой. 3π должен провести вас более 9 раз вокруг стационарного многоугольника.
Мартин Эндер
4
Забавно, как имя класса находится RotatingPolygonsGolfedв «гольфовом» коде, а просто RotatingPolygonsв обычном коде. ;)
Увлечения Кэлвина
1
Вы можете сохранить большую часть символов, просто изменив свой импорт на использование * вместо определенных классов ...
pseudonym117
12

Javascript, 1284 символа (-200 = 1084 символа)

Минимизированный код

function epi(B,r2,r1,n2,n1){K=Math;function C(t){return K.cos(t)}function S(t){return K.sin(t)}function A(y,x){return K.atan2(y,x)}P=K.PI;v=[[],[]];w=[[],[]];z=[];function Z(x,y,j){c=C(t=f*H+P/2);s=S(t);v[j][n]=c*x-s*y;w[j][n]=s*x+c*y;}function E(i){return{x:r1*S(t=p-i*q),y:r1*C(t)};}function D(x,y,X,Y,t){L=A(m.y,m.x);M=K.sqrt(m.x*m.x+m.y*m.y);N=K.sqrt(X*X+Y*Y);O=~~(t*(M>N?M:N)+1);for(i=J;i<=O;i++){J=1;z[n]=f*H+P+t*i/O;Z(x+M*C(T=L+t*i/O),y+M*S(T),0);Z(x+N*C(T=A(Y,X)+t*i/O),y+N*S(T),1);n++}}function F(x,y,n,r,L,s){I.strokeStyle=s;I.beginPath();for(i=0;i<n;i++)I[i?'lineTo':'moveTo'](x+r*C(t=L+(1-2*i)*P/n),y+r*S(t)*W);I.closePath();I.stroke()}p=P/n1;q=2*p;u=P/n2;H=2*u;s2=r2*S(u);g=f=l=n=J=h=0;R=300;while(l<=(B*2+1)*P/H){o=E(0);m=E(h);m.y-=o.y;m.x-=o.x;if(g<s2){D(g,-r2*C(u),-o.x,-o.y,q);h=(h+1)%n1;g+=2*r1*S(p)}else{m.x+=g-s2;D(s2,-r2*C(u),-o.x+g-s2,-o.y,H);g-=s2*2;f=(f+1)%n2;l++}}return function(e_,t,aa,W_){W=aa?-1:1;I=(e=e_).getContext('2d');I.fillStyle='black';I.fillRect(0,0,600,600);W_&1&&F(R,R,n2,r2,0,'white');T=A(w[1][0],v[1][0]);U=V=0;I.strokeStyle='teal';I.beginPath();I.moveTo(R+v[0][0],R+w[0][0]*W);while(U<t){_=A(w[1][V+1],v[1][V+1]);U+=_-T+(_+1<T?2*P:0);T=_;V++;I.lineTo(R+v[0][V],R+w[0][V]*W)}W_&2&&I.stroke();W_&4&&F(R+v[1][V],R+w[1][V]*W,n1,r1,z[V],'red')}}

Полный код

function epi( nr, r2, r1, n2, n1 ) {
function C( t )
    { return Math.cos( t ); }
function S( t )
    { return Math.sin( t ); }
function A( dy, dx )
    { return Math.atan2( dy, dx ); }

var iCCW, e, t_, xs = [[],[]], ys = [[],[]], ts = [], n = 0, iArc0 = 0;

function addpt( x, y, iBin ) {
    var c_ = C(t_ = iFrame*t2 + Math.PI/2 ),
        s_ = S(t_);

    xs[iBin][n] = c_*x-s_*y;
    ys[iBin][n] = s_*x+c_*y;
}

function poly1pt( iP )
    { return { x: r1*S(t_ = t1b2-iP*t1), y: r1*C(t_) }; }

function arc1( P_Arc_, xP_, yP_, xC_, yC_, t ) {
    var dx_, dy_, dxC, dyC;
    var t0 = A( dy_ = P_Arc_.y, dx_ = P_Arc_.x ),
        r_ = Math.sqrt( dx_*dx_ + dy_*dy_ ),
        t0C = A( dyC = yC_, dxC = xC_ ),
        rC = Math.sqrt( dxC*dxC + dyC*dyC ),
        nt = ~~(t*(r_>rC?r_:rC)+1);

    for( var i = iArc0; i <= nt; i++ ) {
        iArc0 = 1;
        ts[n] = iFrame*t2 + Math.PI + t*i/nt;
        addpt( xP_ + r_*C(t_ = t0+t*i/nt), yP_ + r_*S(t_), 0 );
        addpt( xP_ + rC*C(t_ = t0C+t*i/nt), yP_ + rC*S(t_), 1 );
        n++;
    }
}

function poly( x,y, n, r, t0, sColor ) {
    var Cx = e.getContext('2d');
    Cx.strokeStyle = sColor;
    Cx.beginPath();
    for( var i = 0; i < n; i++ )
        Cx[i ? 'lineTo' : 'moveTo']( x + r*C(t_ = t0+(1-2*i)*Math.PI/n), y + r*S(t_)*iCCW );

    Cx.closePath();
    Cx.stroke();
}

var t1b2 = Math.PI/n1,
    t1 = 2*t1b2,
    t2b2 = Math.PI/n2,
    t2 = 2*t2b2,
    s1 = 2*r1*S(t1b2),
    s2 = 2*r2*S(t2b2),
    xPivot = 0,
    iPivot = 0,
    iFrame = 0,
    P_Pivot, P_Arc,
    nFrame = 0;

while( nFrame <= (nr*2+1)*Math.PI/t2 ) {
    P_Pivot = poly1pt( 0 );
    P_Arc = poly1pt( iPivot );
    if( xPivot < s2/2 ) {
        P_Arc.x -= P_Pivot.x;
        P_Arc.y -= P_Pivot.y;
        arc1( P_Arc, xPivot, -r2*C(t2b2), -P_Pivot.x, -P_Pivot.y, t1 );
        iPivot = (iPivot+1) %n1;
        xPivot += s1;
    } else {
        P_Arc.x -= (P_Pivot.x - (xPivot - s2/2));
        P_Arc.y -= P_Pivot.y;
        arc1( P_Arc, s2/2, -r2*C(t2b2), -P_Pivot.x + xPivot - s2/2, -P_Pivot.y, t2 );
        xPivot -= s2;
        iFrame = (iFrame+1) %n2;
        nFrame++;
    }
}

function renderTo( eCanvas, t, isCCW, sWhat ) {
    iCCW = isCCW ? -1 : 1;
    var Cx = (e = eCanvas).getContext('2d');
    Cx.fillStyle = 'black';
    Cx.fillRect( 0,0, 600,600 );

    if( sWhat &1 )
        poly( 300,300, n2, r2, 0, 'white' );

    var tRef = A( ys[1][0], xs[1][0] ),
        tCum = 0,
        i0 = 0;

    Cx.strokeStyle = 'green';
    Cx.beginPath();
    Cx.moveTo( 300+xs[0][0], 300+ys[0][0]*iCCW );
    while( tCum < t ) {
        t_ = A( ys[1][i0+1], xs[1][i0+1] );
        tCum += t_ - tRef + (t_ - tRef < -1 ? 2*Math.PI : 0);
        tRef = t_;
        i0++;
        Cx.lineTo( 300+xs[0][i0], 300+ys[0][i0]*iCCW );
    }
    if( sWhat &2 )
        Cx.stroke();
    if( sWhat &4 )
        poly( 300+xs[1][i0], 300+ys[1][i0]*iCCW, n1, r1, ts[i0], 'red' );
}

return renderTo;
}

Скрипка, чтобы увидеть рутину во всей ее красоте (и продемонстрировать анимацию), найдена на

http://jsfiddle.net/7rv751jy/2/embedded/result/

Сценарий определяет вызываемую функцию, epiкоторая принимает пять перечисленных параметров в OP. epiвозвращает функцию с подписью, (e,t,isCCW,flags)которая принимает аргументы:

  • e - ссылка на элемент HTML5 Canvas размером 600x600, на котором выполняется рендеринг.
  • t- общий угол (в радианах), который центроид второго многоугольника должен охватывать вокруг центроида первого. Передаваемый аргумент не должен превышать в 2 раза числа оборотов, переданных epi.
  • isCCW - логическое значение, указывающее, должна ли трассировка идти в направлении против часовой стрелки (в отличие от часовой стрелки)
  • flags - набор битовых флагов, указывающих, какие элементы должны быть отображены
    • бит 1 - отображать полигон 1, если он установлен
    • бит 2 - рендеринг трассировки, если установлен
    • бит 3 - отображать полигон 2, если он установлен

Функция может быть вызвана любое количество раз с различными наборами аргументов.

Некоторые заметки:

  • Подпрограмма обрабатывает вырожденные случаи, когда n1 = 2и / или n2 = 2. При анимации определенные комбинации длин могут привести к внезапному быстрому продвижению по трассе. Это связано с тем, что кадры анимации индексируются под углом к ​​центроиду второго многоугольника, и d theta poly2 / d тета-центроид становится единичным в тех случаях, когда центроид 2-стороннего poly 2 находится вблизи вершины 2-стороннего poly 1. Однако это не влияет на след.

  • Имена параметров в epiбудут казаться запутанными, так как на протяжении всей разработки я упоминал полигон 1 как «2», а полигон 2 как «1». Когда я понял несоответствие между моим соглашением и соглашением OP, вместо того, чтобы поменять местами все индексы в коде, я просто поменял местами порядок аргументов epi.

  • Скрипка выше импортирует jQuery, но это для обработки пользовательского интерфейса. epiФункция не имеет библиотечных зависимостей.

  • Код обрабатывает следы CCW, просто инвертируя ось Y. Это несколько не элегантно, так как полигон 2 начинается в позиции Y-инвертированной во время следов CCW, но никто не сказал, что рутина должна быть элегантной. ;)

COTO
источник
1
Прекрасный! Я обнаружил, что с полноэкранной ссылкой проще всего работать: jsfiddle.net/7rv751jy/embedded/result
Увлечения Calvin's
Одна крошечная жалоба состоит в том, что вершина трассера не начинается с неподвижной вершины.
Увлечения Кэлвина
Ха. Я полностью упустил это из виду. Я говорю «Ха», потому что код изначально (непреднамеренно) был специфицирован, но я изменил вершину трассы, потому что полагал, что трассировка будет выглядеть лучше, если она начнется немедленно. Я обновил код, чтобы он соответствовал спецификации, и обновил ссылку на скрипку до полноэкранной версии, соответствующей спецификации. В качестве бонуса он сбивает одного персонажа с общего счета.
COTO
Как я могу немного ускорить это? JS нуб здесь.
Сохам Чоудхури
@SohamChowdhury: Измените код nt = ~~(t*(r_>rC?r_:rC)+1)на, nt = ~~(t*(r_>rC?r_:rC)/10+1)и это должно немного ускорить процесс.
COTO