
import java.util.*;
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import javax.swing.event.*;
import javax.swing.border.*;

class Vertex {
    public double x, y, z;

    public Vertex(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Vertex getTranslate(double xt, double yt, double zt) {
        return new Vertex(x + xt, y + yt, z + zt);
    }

    public Vertex getTranslateX(double xt) {
        return getTranslate(xt, 0, 0);
    }

    public Vertex getRotationZ(double alpha) {
        return new Vertex(x * Math.cos(alpha) - y * Math.sin(alpha), x * Math.sin(alpha) + y * Math.cos(alpha), z);
    }

    public Vertex getRotationX(double alpha) {
        return new Vertex(x, y * Math.cos(alpha) - z * Math.sin(alpha), y * Math.sin(alpha) + z * Math.cos(alpha));
    }

    public Vertex getTransforme(double phi, double alpha, double l, double theta) {
        return getRotationZ(phi).getRotationX(alpha).getTranslateX(l).getRotationZ(theta);
    }
}

class Segment {
    public Vertex v1, v2;
    public Segment(Vertex v1, Vertex v2) {
        this.v1 = v1;
        this.v2 = v2;
    }
}

class RenduTore extends JComponent {
    public Vector listePolygone = new Vector();

    public int decalagePolygone = 0;
    public int nombreCotesPolygone = 2;
    public int nombrePolygones = 128;
    public double inclinaison = (90 + 0) / 180d * Math.PI;
    public int coefArrondiMeridien = 1;
    public int coefArrondiPolygone = 1;

    public void setDecalagePolygone(int decalagePolygone) {
        this.decalagePolygone = decalagePolygone;
        repaint();
    }

    public void setNombreCotesPolygone(int nombreCotesPolygone) {
        this.nombreCotesPolygone = nombreCotesPolygone;
        repaint();
    }

    public void setNombrePolygones(int nombrePolygones) {
        this.nombrePolygones = nombrePolygones;
        repaint();
    }
    public void setInclinaison(double inclinaison) {
        this.inclinaison = inclinaison;
        repaint();
    }

    public void setCoefArrondiMeridien(int coefArrondiMeridien) {
        this.coefArrondiMeridien = coefArrondiMeridien;
        repaint();
    }

    public void setCoefArrondiPolygone(int coefArrondiPolygone) {
        this.coefArrondiPolygone = coefArrondiPolygone;
        repaint();
    }

    public void paint(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setBackground(Color.WHITE);
        g2.clearRect(0, 0, getSize().width, getSize().height);

        construisPolygones();
        tracePolygones(g2);
        traceLignes(g2);
    }

    public void construisPolygones() {
        listePolygone.clear();
        Polygone polygoneBase = new Polygone(nombreCotesPolygone, Tore.RAYON_POLYGONE);
        for (int i = 0; i < this.nombrePolygones * this.coefArrondiMeridien; i++) {
            double tour = ((double) i) / ((double) this.nombrePolygones * this.coefArrondiMeridien);
            double theta = 2 * Math.PI * tour;
            double phi = this.decalagePolygone * theta / nombreCotesPolygone;
            listePolygone.add(polygoneBase.getTransforme(phi, this.inclinaison, Tore.RAYON, theta));
        }
    }

    public void tracePolygones(Graphics2D g2) {
        for(int i = 0; i < listePolygone.size(); i++) {
            Polygone p = (Polygone) listePolygone.get(i);
            if (i % this.coefArrondiMeridien ==0)
                tracePolygone(g2, p);
        }
    }

    public void traceLignes(Graphics2D g2) {
        Polygone[] tab = new Polygone[listePolygone.size()];
        listePolygone.toArray(tab);
        for(int i = 0; i < nombreCotesPolygone; i++) {
            for(int j = 0; j < tab.length - 1; j++)
                traceSegment(g2, new Segment(tab[j].getVertex(i), tab[j + 1].getVertex(i)));
            traceSegment(g2, new Segment(tab[tab.length - 1].getVertex(i),
                                         tab[0].getVertex(i + this.decalagePolygone)));
        }
    }


    public void tracePolygone(Graphics2D g, Polygone p) {
        for(Iterator i = p.getSegments().iterator(); i.hasNext();)
            traceSegment(g, (Segment) i.next());
    }

    public void traceSegment(Graphics2D g, Segment s) {
        double[] xy1 = getXY(s.v1);
        double[] xy2 = getXY(s.v2);
        double e1 = getEpaisseur(s.v1);
        double e2 = getEpaisseur(s.v2);

        double[] v12 = new double[] {xy2[0] - xy1[0], xy2[1] - xy1[1]};
        double n = Math.sqrt(Math.pow(v12[0], 2) + Math.pow(v12[1], 2));
        if (n == 0) return;
        double[] u = new double[] {v12[0] / n, v12[1] / n};
        double[] v = new double[] {-u[1], u[0]};

        double[] pA = new double[] {xy1[0] + -e1 * u[0] +  e1 * v[0],   xy1[1] + -e1 * u[1] +  e1 * v[1]};
        double[] pB = new double[] {xy1[0] + -e1 * u[0] + -e1 * v[0],   xy1[1] + -e1 * u[1] + -e1 * v[1]};
        double[] pC = new double[] {xy2[0] +  e2 * u[0] + -e2 * v[0],   xy2[1] +  e2 * u[1] + -e2 * v[1]};
        double[] pD = new double[] {xy2[0] +  e2 * u[0] +  e2 * v[0],   xy2[1] +  e2 * u[1] +  e2 * v[1]};

        GeneralPath gp = new GeneralPath();
        gp.moveTo((float) pA[0], (float) pA[1]);
        gp.lineTo((float) pB[0], (float) pB[1]);
        gp.lineTo((float) pC[0], (float) pC[1]);
        gp.lineTo((float) pD[0], (float) pD[1]);
        g.fill(gp);
    }

    public double[] getXY(Vertex v) {
        return new double[] {
            300 - (v.x * 300) / (v.z + 7),
            300 + (v.y * 300) / (v.z + 7)
        };
    }

    public double getEpaisseur(Vertex v) {
        return Math.max(0, 1.2 * (0.6 - v.z));
    }
}

class Polygone extends Vector {
    public Polygone() {
        super();
    }

    // polygone régulier dans le plan XY
    public Polygone(int nombreCotes, double rayon) {
        for(int i = 0; i < nombreCotes; i++) {
            double angle = (i * 2 * Math.PI) / nombreCotes;
            add(new Vertex(rayon * Math.cos(angle),
                           rayon * Math.sin(angle),
                           0));
        }
    }

    public Vector getSegments() {
        Vector retour = new Vector();
        Vertex[] vertexis = new Vertex[size()];
        toArray(vertexis);
        for(int i = 0; i < vertexis.length - 1; i++)
            retour.add(new Segment(vertexis[i], vertexis[i + 1]));
        retour.add(new Segment(vertexis[vertexis.length - 1], vertexis[0]));
        return retour;
    }

    public Polygone getTransforme(double phi, double alpha, double l, double theta) {
        Polygone retour = new Polygone();
        for(Iterator i = iterator(); i.hasNext();)
            retour.add(((Vertex) i.next()).getTransforme(phi, alpha, l, theta));
        return retour;
    }

    public Vertex getVertex(int i) {
        return (Vertex) get(i % size());
    }
}

class SliderTore extends JSlider {
    public SliderTore(String texte, int min, int max, int valeur) {
        super(min, max, valeur);
        setPaintLabels(true);
//        setPaintTicks(true);
        setSnapToTicks(true);
        int gap = (max - min) / 5;
        setMajorTickSpacing(gap);
        setMinorTickSpacing(1);
        Hashtable lesValeurs = new Hashtable();
        for (int i = min; i <= max; i += gap) lesValeurs.put(new Integer(i), new JLabel(i + ""));
        setLabelTable(lesValeurs);
        setBorder(new TitledBorder(texte));
    }
}

class ControleTore extends JPanel {
    RenduTore renduTore;
    public ControleTore(RenduTore renduTore) {
        this.renduTore = renduTore;
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        final SliderTore sliderDecalage = new SliderTore("Torsion", 0, 31, 0);
	sliderDecalage.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                ControleTore.this.renduTore.setDecalagePolygone(sliderDecalage.getValue());
            }
        });
        final SliderTore sliderNombreCotes = new SliderTore("Nombre côtés", 2, 31, 2);
	sliderNombreCotes.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                ControleTore.this.renduTore.setNombreCotesPolygone(sliderNombreCotes.getValue());
            }
        });
        final SliderTore sliderNombrePolygones = new SliderTore("Nombre polygones", 4, 512, 128);
	sliderNombrePolygones.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                ControleTore.this.renduTore.setNombrePolygones(sliderNombrePolygones.getValue());
            }
        });
        final SliderTore sliderInclinaison = new SliderTore("Inclinaison (en degrés)", -90, 90, 0);
	sliderInclinaison.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                ControleTore.this.renduTore.setInclinaison((90 + sliderInclinaison.getValue()) / 180d * Math.PI);
            }
        });
        final SliderTore sliderCoefArrondiMeridien = new SliderTore("Coef arrondi méridiens", 1, 16, 1);
	sliderCoefArrondiMeridien.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                ControleTore.this.renduTore.setCoefArrondiMeridien(sliderCoefArrondiMeridien.getValue());
            }
        });
        final SliderTore sliderCoefArrondiPolygone = new SliderTore("Coef arrondi polygones", 1, 16, 1);
	sliderCoefArrondiPolygone.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                ControleTore.this.renduTore.setCoefArrondiPolygone(sliderCoefArrondiPolygone.getValue());
            }
        });

        add(sliderDecalage);
        add(sliderNombreCotes);
        add(sliderNombrePolygones);
        add(sliderInclinaison);
        add(sliderCoefArrondiMeridien);
    }
}

class FrameTore extends JFrame {
    public FrameTore() {
        RenduTore renduTore = new RenduTore();
        Container container = getContentPane();
        container.setLayout(new BorderLayout());
        container.add(new ControleTore(renduTore), BorderLayout.WEST);
        container.add(renduTore, BorderLayout.CENTER);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        addWindowListener(new WindowAdapter() {
//        });

    }

}

public class Tore {
    final static double RAYON = 5;
    final static double RAYON_POLYGONE = 1.4142;



    public static void main(String[] args) {
        FrameTore f = new FrameTore();
        f.pack();
        f.setSize(840, 640);
        f.setVisible(true);

    }
}
