Dibujando Epiciclogones

22

Un epicicloide es la curva que forma un punto en un círculo a medida que rueda alrededor de otro círculo. Un cyclogon es la forma que forma un punto en un polígono regular cuando rueda a través de un plano. Un epiciclogón es la curva trazada por un punto en un polígono regular a medida que rueda alrededor de otro.

Escribir un programa que dibuja un epicyclogon dada 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)

Notas

  • Cuando res negativo, el rodillo debe ir en sentido antihorario .
  • Porque r, una revolución ocurre cuando la línea que conecta los centroides de las dos formas barre 360 ​​grados completos. Esta noción se expande para incluir todos los valores der . (Entonces, en un cuarto de revolución, la línea que conecta los centroides se extiende 90 grados).
  • Estos argumentos deben provenir de la línea de comando o su programa debe solicitarlos (por ejemplo, con Python input()).
  • r1y r2son relativos entre sí, no las dimensiones de la imagen. Por lo tanto, puede configurar una "unidad" para que sea cualquier número de píxeles reales.

El punto que debe trazar es uno de los vértices de la forma ondulada. Las formas deben comenzar con este vértice tocando un vértice estacionario y dos lados adyacentes:

ejemplo de epiciclogon

Los vértices iniciales exactos y el ángulo del polígono estacionario no importan.

Salida

El resultado debe ir a una imagen que tenga al menos 600x600 píxeles (o alguna dimensión variable que se pueda establecer en 600). Debe mostrar toda la curva de epiciclogón especificada por los parámetros, bien enmarcada en la imagen.

Los polígonos estacionarios y rodantes también deben dibujarse (con el rodillo en su estado final). Las dos formas y el epiciclogón deben ser tres colores notablemente diferentes.

También debe haber una manera simple de no dibujar los polígonos ( basta con un cambio de truea falseen el código).

Muéstranos al menos 2 imágenes de salida. Está bien reducirlos si es necesario.

Tanteo

El código más corto que produce imágenes de salida válidas gana.

Bonos

  • Menos 50 bytes si la salida es un gif animado (o similar) de la curva que se está dibujando.
  • Menos 150 bytes si deja n1y n2toma el valor 2 para que las formas se conviertan en segmentos de línea de longitud 2 * r1(o r2), "rodando" uno alrededor del otro. La forma en que maneja rcuándo n1y n2son 2 depende de usted, ya que los centroides no giran uno alrededor del otro como lo hacen en otros casos. (No "rodar" en absoluto no cuenta como manejarlo).

Como estoy bastante ansioso por ver esta idea novedosa bien ejecutada (y no es exactamente un juego de niños), voy a otorgar 150 recompensas al ganador. El concurso finalizará el mismo día que se agote la recompensa.

La recompensa no se otorgará al ganador si está claro que simplemente reescribieron la mayor parte del código de otro envío.

Las funciones de biblioteca que ya hacen esto (si hay alguna) no están permitidas.

Nota: Esto vino de mis preguntas sobrantes que cualquiera puede publicar libremente. Pero si nadie más los publica, hay una buena posibilidad de que lo haga a tiempo. :PAGS

Pasatiempos de Calvin
fuente
Creo que en sentido antihorario debería ser positivo en su lugar.
Soham Chowdhury
3
@SohamChowdhury Creo que apenas importa.
Hobbies de Calvin
Tienes razón, en realidad. ¿Tienes alguna imagen de ejemplo? No tengo el reproductor de CDF.
Soham Chowdhury
@ githubphagocyte veo tu punto. Fijo.
Calvin's Hobbies
@ MartinBüttner No es demasiado estricto, fue lo primero que pensé. Puede solicitar valores de otra manera si es necesario.
Aficiones de Calvin

Respuestas:

3

MATLAB: 735 bytes - 200 bonus = 535

Mi programa maneja el caso n = 2 y dibuja una animación en tiempo real. Hay algunas diferencias entre las versiones golfizadas y no golfistas:

La versión sin golf solo tiene una opción para guardar la animación en un archivo 'g.gif', configurando savegif = 1el código. Está desactivado por defecto, ya que puede ser molesto por algunas razones:

  • Creación de un archivo no deseado
  • Posible retraso
  • Se genera un error si tiene varios monitores y la ventana de trazado no está en el correcto ... El ahorro de gif tuvo que descartarse en la versión de golf, ya que tomó alrededor de 100 bytes, excediendo el tamaño de la bonificación.

La versión sin golf dibuja un círculo en el vértice del trazador. También produce más cuadros y movimientos más rápidos (aunque esto se puede ajustar en la versión de golf cambiando los números).

Muestras:

f(11,5,90,2,99,0) después de la finalización del programa

muestra de golf

epic(1.3,4,2,6,6,1) con salida gif

muestra sin golf

Código sin golf

%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

Código de golf

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

Instrucciones:

Guarde la función en un archivo con el mismo nombre, es decir, epic.mo f.m. Ejecútelo llamando a la función desde la consola de Matlab.

Uso: epic(r, r1, r2, n1, n2, dispPoly) donde dispPolyes una variable booleana (cero si es falso, un número distinto de cero si es verdadero) que determina si se dibujan los polígonos.

Editar: Se agregó una bonificación de 50 por imagen animada.

Feersum
fuente
14

Java - 2,726 2,634 - 200 = 2434 caracteres

Mejorado de 3800 bytes ish

Gracias a todos por sus sugerencias (especialmente pseudonym117), aquí está la nueva versión.

Agregué una clase P que es la clase de puntos y una clase L que extiende ArrayList

También agregué algunos cambios menores de lógica.

Aquí está la clase principal (no golfizada):

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);}
    }
}

Y la versión de golf:

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);}}}

Además de las clases 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;}}

Y yo:

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());}}

Cambie int d a 0 o 1 para mostrar polígonos

argumentos - 1 100 50 5 2

ingrese la descripción de la imagen aquí

args - 1.5 100 100 7 3

ingrese la descripción de la imagen aquí

args - 2 40100 3 7

ingrese la descripción de la imagen aquí

Estiramiento maniaco
fuente
¿Es rrealmente 50 en todos tus ejemplos? Eso significaría que el rodillo da vueltas 50 veces.
Aficiones de Calvin
El nuevo ejemplo de @ Calvin'sHobbies muestra pi * 3
Stretch Maniac el
1
@StretchManiac Eso no puede ser correcto. 3π debería llevarte un poco más de 9 veces alrededor del polígono estacionario.
Martin Ender
44
Es curioso cómo el nombre de la clase está RotatingPolygonsGolfeden el código "golfed" mientras está solo RotatingPolygonsen el código normal. ;)
Calvin's Hobbies
1
usted puede ahorrar una buena cantidad de personajes con sólo cambiar sus importaciones para uso * en lugar de clases específicas ...
pseudonym117
12

Javascript, 1284 caracteres (-200 = 1084 caracteres)

El código minimizado es

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')}}

El código completo es

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;
}

Un violín para contemplar la rutina en toda su gloria poligonal (y para demostrar la animación) se encuentra en

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

El script define una función llamada epique acepta los cinco parámetros enumerados en el OP. epidevuelve una función con la firma (e,t,isCCW,flags)que acepta argumentos:

  • e - una referencia a un elemento de lienzo HTML5 de 600x600 en el que representar
  • t- el ángulo total (en radianes) que el centroide del segundo polígono debe barrer alrededor del centroide del primero. El argumento pasado no debe exceder 2 pi veces el número de rotaciones pasado epi.
  • isCCW - booleano que indica si la traza debe proceder en el sentido contrario a las agujas del reloj (en lugar de en sentido horario)
  • flags - un conjunto de banderas de bits que indican qué elementos deben representarse
    • bit 1 - renderizar el polígono 1 si está configurado
    • bit 2 - renderizar traza si está configurado
    • bit 3 - renderizar el polígono 2 si está configurado

La función se puede llamar cualquier número de veces con diferentes conjuntos de argumentos.

Algunas notas:

  • La rutina maneja los casos degenerados donde n1 = 2y / o n2 = 2. Al animar, ciertas combinaciones de longitudes causarán avances rápidos repentinos en la traza. Esto se debe a que los cuadros de animación están indexados por el ángulo del centroide del segundo polígono, y d theta poly2 / d theta centroid se vuelve singular en los casos en que el centroide de 2 lados poli 2 está cerca de un vértice de 2 lados poli 1 Sin embargo, esto no afecta la traza.

  • Los nombres de los parámetros en epiparecerán confusos ya que durante todo el desarrollo, me referí al polígono 1 como "2" y al polígono 2 como "1". Cuando me di cuenta de la inconsistencia entre mi convención y la del OP, en lugar de intercambiar todos los índices en el código, simplemente cambié el orden de los argumentos epi.

  • El violín anterior importa jQuery, pero esto es para manejar la interfaz de usuario. La epifunción no tiene dependencias de biblioteca.

  • El código maneja los rastros CCW simplemente invirtiendo el eje Y. Esto es algo poco elegante ya que el polígono 2 comienza en una posición invertida en Y durante las trazas CCW, pero nadie dijo que la rutina tenía que ser elegante. ;)

COTO
fuente
1
¡Hermosa! Descubrí que es más fácil trabajar con el enlace de pantalla completa: jsfiddle.net/7rv751jy/embedded/result
Calvin's Hobbies
Una pequeña queja es que el vértice marcador no comienza en un vértice estacionario.
Hobbies de Calvin
Decir ah. Lo pasé por alto por completo en las especificaciones. Digo 'Ha' porque el código fue originalmente (sin darse cuenta) a las especificaciones, pero cambié el vértice de rastreo porque pensé que el rastreo se vería mejor si comenzara de inmediato. He actualizado el código para que sea específico, y actualicé el enlace al violín a una versión de pantalla completa que cumpla con las especificaciones. Como beneficio adicional, elimina a un personaje del recuento total.
COTO
¿Cómo puedo acelerarlo un poco? JS noob aquí.
Soham Chowdhury
@SohamChowdhury: Cambiar el código nt = ~~(t*(r_>rC?r_:rC)+1)de nt = ~~(t*(r_>rC?r_:rC)/10+1)y debe acelerar un poco las cosas.
COTO