Powered by PulpCore

Sketch.java 

// Sketch
// Move your mouse to draw. Click to erase.
import pulpcore.image.CoreGraphics;
import pulpcore.image.BlendMode;
import static pulpcore.image.Colors.*;
import pulpcore.Input;
import pulpcore.math.CoreMath;
import pulpcore.scene.Scene;
import pulpcore.Stage;

public class Sketch extends Scene {
    int[] colors = { rgb(0, 2, 19), rgb(4, 14, 0), rgb(15, 0, 21), rgb(21, 4, 7), rgb(19, 18, 1) };
    Particle[] particles;
    int particleIndex;
    int nextFade;
    int lastX, lastY;
    boolean erase;
    boolean wasMouseInside;
    boolean firstTime = true;
    
    @Override
    public void load() {
        particles = new Particle[5000];
        int numParticlesPerAttractor = 500;
        for (int i = 0; i < particles.length; i++) {
            boolean visible = firstTime && i < numParticlesPerAttractor*2;
            int color = colors[(i / numParticlesPerAttractor) % colors.length];
            particles[i] = new Particle(visible, color);
        }
        for (int i = 0; i < particles.length; i++) {
            int j = (i + particles.length - 1) % particles.length;
            j = j - (j % numParticlesPerAttractor);
            particles[i].attractor = particles[j];
        }
        erase = true;
        if (firstTime) {
            firstTime = false;
            particleIndex = numParticlesPerAttractor*2;
        }
        Input.setCursor(Input.CURSOR_OFF);
    }
    
    @Override
    public void updateScene(int elapsedTime) {
        if (Input.isMousePressed()) {
            load();
        }
        else {
            // Draw
            int x = Input.getMouseX();
            int y = Input.getMouseY();
            if (wasMouseInside && (x != lastX || y != lastY)) {
                makeParticles(lastX, lastY, x, y);
            }
            lastX = x;
            lastY = y;
            wasMouseInside = Input.isMouseInside();
        
            // Update particles
            elapsedTime = Math.min(elapsedTime, 20);
            for (Particle particle : particles) {
                particle.update(elapsedTime);
            }
            nextFade -= elapsedTime;
        }
    }
    
    @Override
    public void drawScene(CoreGraphics g) {
        if (erase) {
            erase = false;
            g.setColor(BLACK);
            g.fill();
        }
        
        // Draw particles to surface
        g.setBlendMode(BlendMode.Add());
        for (Particle particle : particles) {
            particle.draw(g);
        }
        
        // Slowly fade
        if (nextFade <= 0) { 
            nextFade = 50;
            g.setBlendMode(BlendMode.SrcOver());
            g.setColor(BLACK);
            g.setAlpha(0x06);
            g.fill();
        }
    }
    
    void makeParticles(int x1, int y1, int x2, int y2) {
        int numParticles = 2*(int)Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); 
        for (int i = 0; i < numParticles; i++) {
            double x = x1 + i * (x2 - x1) / (double)numParticles;
            double y = y1 + i * (y2 - y1) / (double)numParticles;
            particles[particleIndex].init(x, y);
            particleIndex = (particleIndex + 1) % particles.length;
        }
    }
    
    // Start the particle the middle, but don't draw until init()
    static class Particle {
        double x = Stage.getWidth() / 2 + CoreMath.rand(-50, 50);
        double y = Stage.getHeight() / 2 + CoreMath.rand(-50, 50);
        boolean alive = false;
        double velocityX, velocityY;
        double lastX, lastY;
        Particle attractor;
        int color;
        
        Particle(boolean visible, int color) {
            this.color = color;
            if (visible) {
                init(x, y);
            }
        }

        void init(double x, double y) {
            this.x = x;
            this.y = y;
            this.lastX = x;
            this.lastY = y;
            this.velocityX = CoreMath.rand(-20, 20);
            this.velocityY = CoreMath.rand(-20, 20);
            alive = true;
        }
        
        void update(int elapsedTime) {
            double dt = elapsedTime / 1000.0;
            double dx = attractor.x-x;
            double dy = attractor.y-y;
            double dSq = dx*dx + dy*dy;
            if (dSq > 0) {
                double s = 400 / dSq;
                if (dSq < 25 * 25) {
                    s = -s / 4;
                }
                velocityX += s*dx;
                velocityY += s*dy;
            }
            velocityX *= 0.97;
            velocityY *= 0.97;
            x += velocityX * dt;
            y += velocityY * dt;
        }
        
        void draw(CoreGraphics g) {
            if (alive) {
                g.setColor(color);
                g.drawLine(lastX, lastY, x, y);
                lastX = x;
                lastY = y;
            }
        }
    }
}