import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import moonlander.library.*; 
import ddf.minim.*; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class pure extends PApplet {



//===============================
int[] palette = {0xff6EB9BC, 0xffFEC18B, 0xffFA8A88, 0xff91CD99, 0xff77BE92};//{#B0BA9F, #FC1A79, #655A61};


int IMAGES = 19;
PImage[] images = new PImage[IMAGES]; 
PImage vignette;
//===============================
int FPS = 60;
String tune = "ko0x_-_guiding_lights.mp3";
float BPM = 173.0f;
//===============================

Moonlander _moon;

float BPS = 60.0f / BPM;
int LAST_ROW = 224;

//PShader _toon;
float tickTime = 0;

PGraphics back;
PGraphics l1;
PGraphics l2;
PGraphics l3;

public void setup() {
    
    frameRate(FPS);
    //noiseSeed(seed);
    //size(960, 540, P3D);
    

    back = createGraphics(width, height, P3D);
    l1 = createGraphics(width, height, P3D);
    l2 = createGraphics(width, height, P3D);
    l3 = createGraphics(width, height, P3D);

    //_toon = loadShader("ToonFrag.glsl", "ToonVert.glsl");
    _moon = Moonlander.initWithSoundtrack(this, tune, PApplet.parseInt(BPM), 8);

    //hint(ENABLE_DEPTH_SORT);
    //hint(DISABLE_OPTIMIZED_STROKE);
    hint(ENABLE_OPTIMIZED_STROKE);
    //hint(DISABLE_DEPTH_TEST);
    hint(ENABLE_DEPTH_TEST);

    for (int i = 0; i < IMAGES; i++) {
        String imageName = i + ".png";
        images[i] = loadImage(imageName);
    }

    vignette = loadImage("vignette.png");

    _moon.start();
}

public void draw() {
    _moon.update();

    float eyeX = (float)_moon.getValue("eyeX");
    float eyeY = (float)_moon.getValue("eyeY");
    float eyeZ = (float)_moon.getValue("eyeZ");
    float centerX = (float)_moon.getValue("centerX");
    float centerY = (float)_moon.getValue("centerY");
    float centerZ = (float)_moon.getValue("centerZ");
    float upX = (float)_moon.getValue("upX");
    float upY = (float)_moon.getValue("upY");
    float upZ = (float)_moon.getValue("upZ");
    float FOV1 = (float)_moon.getValue("FOV1");
    float FOV2 = (float)_moon.getValue("FOV2");
    float FOV3 = (float)_moon.getValue("FOV3");
    //camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)



    /*
    float shader_fraction = (float)_moon.getValue("shader_fraction");
     if (shader_fraction > 0.0) {
     shader(_toon);
     noStroke();
     _toon.set("fraction", shader_fraction);
     }
     */
    //background

    //layer1
    background(lerpColor(0xffFFDE4D, palette[0], (float)_moon.getValue("colorFade")));
    l1.beginDraw();
    l1.clear();
    l1.camera(width/2.2f + eyeX, height/1.5f + eyeY, (height/2.0f) / tan(PI*30.0f / FOV1) - width/4 - eyeZ, (width/2.0f)*centerX, (height/2.0f)*centerY, centerZ, 0*upX, 1*upY, 0*upZ);
    switchBlendModes(l1, (int)_moon.getValue("blendMode"));
    switchScenes(l1, (int)_moon.getValue("sceneIndex"));
    l1.endDraw();

    float kaleido = (float)_moon.getValue("kaleido");
    if (kaleido > 0.0f) {
        kaleido(l1);
        kaleido(l2);
    }

    //l2
    l2.beginDraw();
    l2.clear();
    l2.camera(width/2.2f + eyeX, height/1.5f + eyeY, (height/2.0f) / tan(PI*30.0f / FOV2) - width/4 - eyeZ, (width/2.0f)*centerX, (height/2.0f)*centerY, centerZ, 0*upX, 1*upY, 0*upZ);
    switchBlendModes(l2, (int)_moon.getValue("blendMode2"));
    switchScenes(l2, (int)_moon.getValue("sceneIndex2"));
    l2.endDraw();

    int[] blendmodes = {BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, EXCLUSION, MULTIPLY, SCREEN, REPLACE};

    tint(255, 255);
    blendMode(blendmodes[0]);
    image(l1, 0, 0);
    blendMode(blendmodes[max(0, (int)_moon.getValue("blendCombine")) % blendmodes.length]);
    tint(255, round(255 * (float)_moon.getValue("alpha2")));
    image(l2, 0, 0);

    //l3
    l3.beginDraw();
    l3.clear();
    l3.camera(width/2.2f + eyeX, height/1.5f + eyeY, (height/2.0f) / tan(PI*30.0f / FOV3) - width/4 - eyeZ, (width/2.0f)*centerX, (height/2.0f)*centerY, centerZ, 0*upX, 1*upY, 0*upZ);
    switchBlendModes(l3, (int)_moon.getValue("blendMode3"));
    switchScenes(l3, (int)_moon.getValue("sceneIndex3"));
    l3.endDraw();

    //tint(255, 255);
    //blendMode(blendmodes[0]);
    //image(l1, 0, 0);
    blendMode(blendmodes[max(0, (int)_moon.getValue("blendCombine2")) % blendmodes.length]);
    tint(255, round(255 * (float)_moon.getValue("alpha3")));
    image(l3, 0, 0);


    if ((int)_moon.getValue("imgIdx") > -1) {
        tint(255, round(255 * (float)_moon.getValue("imgAlpha")));
        blendMode(blendmodes[max(0, (int)_moon.getValue("blendImg")) % blendmodes.length]);
        PImage img = images[max(0, (int)_moon.getValue("imgIdx")) % IMAGES];
        image(img, 0, 0, width, height);
    }
    
    blendMode(MULTIPLY);
    image(vignette,0,0,width,height);
    
    glow();
}

public void switchBlendModes(PGraphics l, int blendMode) {
    blendMode = max(0, min(blendMode, 9));
    switch(blendMode) {
    case 0:
        l.blendMode(BLEND);
        break;

    case 1:
        l.blendMode(ADD);
        break;

    case 2:
        l.blendMode(SUBTRACT);
        break;

    case 3:
        l.blendMode(DARKEST);
        break;

    case 4:
        l.blendMode(LIGHTEST);
        break;

    case 5:
        l.blendMode(EXCLUSION);
        break;

    case 6:
        l.blendMode(MULTIPLY);
        break;

    case 7:
        l.blendMode(SCREEN);
        break;

    case 8:
        l.blendMode(REPLACE);
        break;
    }
}

public void switchScenes(PGraphics l, int sceneIndex) {

    sceneIndex = max(0, min(sceneIndex, 9));

    tickTime = (float)_moon.getCurrentRow() / LAST_ROW;

    switch(sceneIndex) {
    case 0:
        //scene_intro();
        break;
    case 1:
        scene_3D_gundamface(l);
        break;

    case 2:
        scene_diamond(l);
        break;

    case 3:
        scene_torus_tunnel(l);
        break;

    case 4:
        scene_cube_with_vignette(l);
        break;

    case 5:
        scene_torus_tunnel_side(l);
        break;

    case 6:
        background_circle(l);
        break;
    }
}

public void glow() {
    int glow = (int)_moon.getValue("glow");
    if (glow > 0) {
        float offset = 1;
        blendMode(LIGHTEST);
        tint(255, 10);
        for (int i = 0; i < glow; i++) {
            offset += .01f;
            copy(
                0, 0, width, height, 
                PApplet.parseInt((width - (width * offset))/2), PApplet.parseInt((height - (height * offset))/2), PApplet.parseInt(width * offset), PApplet.parseInt(height * offset));
        }
        blendMode(BLEND);
    }
}

public void scene_3D_gundamface(PGraphics l) {

    l.background(lerpColor(0xffFFDE4D, palette[0], (float)_moon.getValue("colorFade")));

    //blendMode(MULTIPLY);
    l.strokeWeight(1);

    float cameraZ = ((height/2.0f) / tan(PI*60.0f/360.0f));
    l.perspective(PI/1.7f, width/height, cameraZ/90.0f, cameraZ*90.0f);

    randomSeed((long)_moon.getValue("seed"));

    l.rotateZ(radians(7));

    for (int i = 0; i < 30; i++) {
        int colorIndex = PApplet.parseInt(random(palette.length));
        int colorIndex2 = PApplet.parseInt(random(palette.length));
        float offsetX = random(width/5);
        float offsetY = random(height/3);
        float offsetZ = random(width/3);
        float rotX = random(1) > .5f ? -1 : 1;
        float rotY = random(1) > .5f ? -1 : 1;
        float rotZ = random(1) > .5f ? -1 : 1;
        int idx = PApplet.parseInt(random(7));
        float diameter = ((random(1) * width/5) + width/20) / 2;
        l.fill(palette[colorIndex2]);
        l.stroke(palette[colorIndex]);

        l.pushMatrix();
        l.translate(width/2 - offsetX, height/1.5f - offsetY, offsetZ);
        l.rotateZ(radians(tickTime * 360.0f * rotZ + i/10) + radians((float)_moon.getValue("rotZ")));
        l.rotateY(radians(tickTime * 360.0f * rotY + i/10) + radians((float)_moon.getValue("rotY")));
        l.rotateX(radians(tickTime * 360.0f * rotX + i/10) + radians((float)_moon.getValue("rotX")));

        switch(idx) {
        case 0:
            torus(diameter, diameter/3, l);
            break;

        case 1:
            l.box(diameter);
            break;

        case 2:
            cylinder(5, 0, diameter, diameter*2, l); 
            break;

        case 3:
            l.sphereDetail(8);
            l.sphere(diameter/2); 
            break;

        case 4:
            l.box(diameter, diameter/3, diameter*3);
            break;

        case 5:

            for (int c = 1; c < 8; c++) {
                float w = diameter/15;
                l.translate(0, c * w);
                l.box(w, w, diameter*3);
            }
            break;

        case 6:
            for (int x = 0; x < 20; x++) {
                for (int y = 0; y < 10; y++) {
                    l.pushMatrix();
                    float w = width/50;
                    l.translate(x * w, y * w);  
                    cylinder(6, w/2, w/2, w/4, l); 
                    l.popMatrix();
                }
            }
            break;
        }

        l.popMatrix();
    }
    l.blendMode(NORMAL);
    l.copy(
        0, 0, width/2, height, 
        width, 0, -width/2, height);
}

public void scene_diamond(PGraphics l) {

    l.noStroke();

    l.pushMatrix();
    l.fill(0xffff0000);
    l.sphere(10);
    l.popMatrix();

    //middle torus
    l.pushMatrix();
    l.fill(palette[0]);
    l.stroke(palette[0]);
    l.translate(width/2, height/2, width/3);

    l.rotateX(radians(180));
    l.rotateZ(radians(360 * tickTime));

    cylinder(30, width*2, 0, width*2, l);
    l.popMatrix();

    l.pushMatrix();
    l.translate(width/2, width/3.9f );
    l.rotateX(radians(90));
    l.rotateZ(radians(360*2 * tickTime));
    l.fill(palette[2]);
    l.stroke(palette[2]);
    cylinder(5, width/4, 0, width/4, l);
    l.popMatrix();

    //---

    l.pushMatrix();
    l.translate(width/2, width/2 - width/17.99f);
    l.rotateX(radians(90));
    l.rotateZ(radians(360*1.5f * tickTime));
    l.fill(palette[1]);
    l.stroke(palette[1]);
    cylinder(6, width/9, 0, width/9, l);
    l.popMatrix();

    l.pushMatrix();
    l.translate(width/2, width/2 + width/17.99f);
    l.rotateX(radians(-90));
    l.rotateZ(-radians(360*1.5f * tickTime));
    l.fill(palette[1]);
    l.stroke(palette[1]);
    cylinder(6, width/9, 0, width/9, l);
    l.popMatrix();

    //--
    l.pushMatrix();
    l.translate(width/2, width/5 * 3.7f);
    l.rotateX(radians(-90));
    l.rotateZ(-radians(360*2 * tickTime));
    l.fill(palette[2]);
    l.stroke(palette[2]);
    cylinder(5, width/4, 0, width/4, l);
    l.popMatrix();
}

public void scene_torus_tunnel(PGraphics l) {
    l.noStroke();

    l.background(palette[0]);

    float dirY = 1;
    float dirX = 1;

    //directionalLight(width/2, height/2, width/3+400, -dirX, -dirY, -1);

    l.fill(palette[4]);
    l.stroke(palette[4]);

    //middle torus
    l.pushMatrix();
    l.fill(palette[2]);
    l.stroke(palette[2]);
    l.translate(width/2, height/2, width/3);

    l.rotateX(radians(90));
    l.rotateY(radians(90));
    l.rotateZ(radians(360*2 * tickTime));

    torus(width/3, width/10, l);
    torus(width/20, width/3, l);
    l.popMatrix();
}

int offset = 0;
int previousOffset = 0;
int refreshX = 0;
int refreshY = 0;
public void scene_cube_with_vignette(PGraphics l) {
    float FOV = (float)_moon.getValue("FOV");
    l.camera(width/2.0f, height/2.0f, (height/2.0f) / tan(PI*30.0f / FOV), width/2.0f, height/2.0f, 0, 0, 1, 0);
    if ((int)_moon.getValue("scene_cube-offset") != offset) {
        previousOffset = offset;
        offset = (int)_moon.getValue("scene_cube-offset");//round(noise(refreshX, refreshY)*1000);
        refreshX++;
    }

    int min = 0;
    float max = palette.length-1;

    int sides = 12;
    float angle = 360 / sides;

    int nX = offset;
    int nY = offset;
    float cube_radius = (50.0f/640 * width);
    l.stroke(palette[palette.length-1]);

    float SWITCH_EVERY = 8.0f;
    float switchTime = min(1, (tickTime % SWITCH_EVERY) / ((SWITCH_EVERY + 0.0f) / 2.0f));

    // switchTime = min(1, switchTime * switchTime * switchTime * switchTime * switchTime);

    for (int offsets = 0; offsets < 2; offsets++) {
        float offset_rot = 15 * offsets;
        int amount_elements = 16;

        switch(offsets) {
        case 0:
            amount_elements = 16;
            break;
        case 1:
            amount_elements = 4;
            break;
        }

        for (int elements = 0; elements < amount_elements; elements++) {

            for (int i = 0; i < sides; i++) {

                l.strokeWeight(1);

                float radius = (cube_radius/2) + noise(nX, nY + elements + offsets) * width/2;
                float x = cos( radians( i * angle ) + radians(offset_rot)) * radius;
                float y = sin( radians( i * angle ) + radians(offset_rot)) * radius;

                float radiusPrev = (cube_radius/2) + noise(previousOffset, previousOffset + elements + offsets) * width/2;
                float xPrev = cos( radians( i * angle ) + radians(offset_rot)) * radiusPrev;
                float yPrev = sin( radians( i * angle ) + radians(offset_rot)) * radiusPrev;

                x = lerp(xPrev, x, switchTime);
                y = lerp(yPrev, y, switchTime);

                l.pushMatrix();
                l.translate(width/2 + x, height/2 + y, 10);
                l.rotateX(radians(-90) + radians(offset_rot));
                l.rotateY(radians( i * angle ) + radians(90) + radians(offset_rot));
                l.rotateZ(radians( i * angle ) + (radians(360) / tickTime));

                min = 0;
                max = 5;
                int ele = floor(noise(1 + elements*10.0f, 1 + elements + offsets) * (max - min + 1)) + min;

                min = 1;
                max = palette.length - 1;
                int colIndexPrevious = floor(noise(previousOffset + elements * 10.0f, previousOffset + elements + offsets) * (max - min + 1)) + min;
                int colIndex = floor(noise(nX + elements * 10.0f, nY + elements + offsets) * (max - min + 1)) + min;

                fill(lerpColor(palette[colIndexPrevious], palette[colIndex], switchTime));

                float diameter = (noise(nX + elements * 100, nY + elements + offsets)) * (30.0f/640 * width);
                float diameterPrev = (noise(previousOffset + elements * 100, previousOffset + elements + offsets)) * (30.0f/640 * width);

                float len = (noise(nX + elements * 100, nY + elements + offsets)) * (190.0f/640 * width);
                float lenPrev = (noise(previousOffset + elements * 100, previousOffset + elements + offsets)) * (190.0f/640 * width);

                switch(ele) {
                case 0:
                    cylinder(elements % 2 == 0 ? 3 : 8, lerp(diameterPrev, diameter, switchTime), lerp(diameterPrev, diameter, switchTime)/3, lerp(lenPrev, len, switchTime), l);
                    break;

                case 1:
                    cylinder(elements % 2 == 0 ? 8 : 3, lerp(diameterPrev, diameter, switchTime)/5, lerp(diameterPrev, diameter, switchTime), lerp(lenPrev, len, switchTime), l);
                    break;

                case 2:
                    l.sphereDetail(elements+2);
                    l.rotateX(radians(90) + (radians(360) / tickTime));
                    l.sphere(lerp(diameterPrev, diameter, switchTime));
                    break;

                case 3:
                    l.sphereDetail(floor(elements*1.5f)+2);
                    l.rotateX(-radians(90) + (radians(360) / tickTime));
                    l.sphere(lerp(diameterPrev, diameter, switchTime));
                    break;

                case 4:
                    l.box(lerp(diameterPrev, diameter, switchTime));
                    break;

                case 5:
                    l.box(lerp(diameterPrev, diameter, switchTime), lerp(diameterPrev, diameter, switchTime)*3, lerp(diameterPrev, diameter, switchTime)*3);
                    break;
                }
                l.popMatrix();
            }
        }
    }

    //postProcessing();

    kaleido(l);

    //centercube
    l.noFill();
    l.pushMatrix();
    l.stroke(0xffF2EBBF);
    l.strokeWeight(15);
    l.strokeJoin(MITER);
    l.strokeCap(PROJECT);
    l.translate(width/2, height/2, 40.0f/640*width);//(300.0/640 * width));
    l.rotateY( radians(360) / tickTime );
    l.rotateZ( radians(360) / tickTime );
    //sphereDetail(6);
    l.box(cube_radius/.7f);
    l.popMatrix();
    //end - centercube
    //centercube
    l.fill(palette[palette.length - 1]);
    l.pushMatrix();
    l.stroke(0xffF2EBBF);
    l.strokeWeight(1);
    l.translate(width/2, height/2, 90.0f/640*width);//(300.0/640 * width));
    l.rotateY( radians(360) / tickTime );
    l.rotateZ( radians(360) / tickTime );
    //sphereDetail(6);
    l.box(cube_radius/1.01f);
    l.popMatrix();
    //end - centercube
}

public void scene_torus_tunnel_side(PGraphics l) {
    l.fill(palette[palette.length - 1]);
    l.stroke(0xffF2EBBF);

    float FOV = (float)_moon.getValue("FOV");
    l.camera(
        0, height/2.0f-100, (height/2.0f) / tan(PI * 90.0f / FOV), 
        width/2.0f, height/2.0f + 100, 0, 
        0, 1, 1);

    float radius = 200 / 640.0f * width;
    float girth = radius/5;

    int amount_x = 12;
    int amount_y = 12;
    int amount_z = 12;
    float offset = 0;

    float tOff = radians(tickTime * 360.0f);

    for (int z = 0; z < amount_z; z++) {
        for (int x = 0; x < amount_x; x++) {

            float offsetZ      = sin(TAU/amount_z * z) * girth;
            float offsetRadius = cos(TAU/amount_z * z) * girth;

            float rotX = sin(TAU/amount_x * x + tOff);
            float rotY = cos(TAU/amount_x * x + tOff);
            float offsetX = rotX * (radius + offsetRadius);
            float offsetY = rotY * (radius + offsetRadius);

            l.pushMatrix();
            l.translate(width/2, height/2, 0);

            l.translate(offsetX, offsetY, offsetZ);

            //sphereDetail(4);
            //sphere(10/640.0 * width);
            torus(10/640.0f * width, 5/640.0f * width, l);
            //box(15);

            l.popMatrix();
        }
    }

    l.pushMatrix();
    l.rotateX(tOff);
    l.rotateY(tOff);
    l.translate(width/2, height/2);
    l.popMatrix();
}

public void kaleido(PGraphics l) {

    //top left
    l.copy(get(), 
        0, 0, width/2, height/2, 
        width/2, height/2, -width/2, -height/2);

    //top right
    l.copy(get(), 
        width/2, 0, width/2, height/2, 
        width, height/2, -width/2, -height/2);

    //bottom left
    l.copy(get(), 
        0, height/2, width/2, height/2, 
        width/2, height, -width/2, -height/2);

    //bottom right
    l.copy(get(), 
        width/2, height/2, width/2, height/2, 
        width, height, -width/2, -height/2);
}

public void torus(float radius, float tube_radius, PGraphics l) {

    if (tube_radius == 0)
        tube_radius = radius/2;

    int circle_parts = 8;
    float circle_angle = radians(360.0f / circle_parts);

    int tube_segments = 22;
    float tube_angle = radians(360.0f / tube_segments);

    for (int seg = 0; seg < tube_segments; seg += 1) {

        l.beginShape(QUAD_STRIP);

        for (int idx = 0; idx < circle_parts + 1; idx += 2) {

            float x1 = (radius + (tube_radius * cos(idx * circle_angle+ radians(45)))) * cos(seg * tube_angle + radians(45));
            float y1 = (radius + (tube_radius * cos(idx * circle_angle+ radians(45)))) * sin(seg * tube_angle + radians(45));
            float z1 = tube_radius * sin(idx * circle_angle + radians(45));

            float x1Next = (radius + (tube_radius * cos(idx * circle_angle+ radians(45)))) * cos((seg+1+ radians(45)) * tube_angle);
            float y1Next = (radius + (tube_radius * cos(idx * circle_angle+ radians(45)))) * sin((seg+1+ radians(45)) * tube_angle);
            float z1Next = tube_radius * sin(idx * circle_angle + radians(45));

            float off = radius;
            float xOff = 0;//noise(radius, 0) * off;
            float yOff = 0;//noise(radius, 0) * off;
            float zOff = 0;//noise(radius, 0) * off;

            l.vertex(x1     + xOff, y1     + yOff, z1     + zOff);
            l.vertex(x1Next + xOff, y1Next + yOff, z1Next + zOff);
        }

        l.endShape();
    }
}

public void cylinder( int sides, float r1, float r2, float h, PGraphics l) {
    float angle = 360 / sides;
    float halfHeight = h / 2;
    // top
    l.beginShape();
    for (int i = 0; i < sides; i++) {
        float x = cos( radians( i * angle ) ) * r1;
        float y = sin( radians( i * angle ) ) * r1;
        l.vertex( x, y, -halfHeight);
    }
    l.endShape(CLOSE);
    // bottom
    l.beginShape();
    for (int i = 0; i < sides; i++) {
        float x = cos( radians( i * angle ) ) * r2;
        float y = sin( radians( i * angle ) ) * r2;
        l.vertex( x, y, halfHeight);
    }
    l.endShape(CLOSE);
    // draw body
    l.beginShape(TRIANGLE_STRIP);
    for (int i = 0; i < sides + 1; i++) {
        float x1 = cos( radians( i * angle ) ) * r1;
        float y1 = sin( radians( i * angle ) ) * r1;
        float x2 = cos( radians( i * angle ) ) * r2;
        float y2 = sin( radians( i * angle ) ) * r2;
        l.vertex( x1, y1, -halfHeight);
        l.vertex( x2, y2, halfHeight);
    }
    l.endShape(CLOSE);
} 

public void background_circle(PGraphics l) {
    l.beginDraw();
    l.clear();

    int amount = 20;
    //int amount_y = 12;
    float dist_x = width/amount;
    float dist_y = width/amount;
    //float diameter = dist_x/4;

    //l.noFill();
    l.stroke(90);
    float diameter = (width/amount) / 2;
    for (int x = -5; x < amount+5; x++) {
        for (int y = -5; y < amount+5; y++) {
            l.pushMatrix();
            l.translate(x * dist_x + (y % 2 == 0 ? dist_x/2 : 0), y * dist_y);
            l.rotateX(radians(x*15) + radians(y*15) + radians(360/1 * tickTime));
            l.rotateY(radians(360*2 * tickTime) + radians(x*3) + radians(y*3));
            l.rotateZ(radians(360*2 * tickTime) + radians(x*3) + radians(y*3));
            l.rotateX(-radians(360*2 * tickTime) + radians(x*3) + radians(y*3));
            cylinder(3, 0, diameter, diameter/3, l);
            l.popMatrix();
        }
    }
    l.endDraw();
}

    public void settings() {  size(1920, 1080, P3D);  smooth(8); }
    static public void main(String[] passedArgs) {
        String[] appletArgs = new String[] { "--present", "--window-color=#080808", "--hide-stop", "pure" };
        if (passedArgs != null) {
          PApplet.main(concat(appletArgs, passedArgs));
        } else {
          PApplet.main(appletArgs);
        }
    }
}
