Skip to content

Instantly share code, notes, and snippets.

@KrabCode
Last active November 9, 2024 10:59
Show Gist options
  • Save KrabCode/f5183a961fdae1725441a7ab1a367822 to your computer and use it in GitHub Desktop.
Save KrabCode/f5183a961fdae1725441a7ab1a367822 to your computer and use it in GitHub Desktop.
A calendar of 365 moons in different phases as a looping trippy visual.
import com.krab.lazy.*;
LazyGui gui;
String[] monthNames = new String[]{
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
void setup() {
//size(1200, 800, P2D);
fullScreen(P2D);
smooth(8);
frameRate(60);
colorMode(HSB, 1, 1, 1, 1);
gui = new LazyGui(this);
}
void draw() {
float t = gui.slider("time");
float speed = gui.slider("speed", 0.5);
gui.sliderSet("time", t + radians(speed));
float basePhase = (t * TAU) % TAU;
background(gui.colorPicker("bg").hex);
float gridPosX = gui.slider("grid x");
float gridStepX = gui.slider("grid step x", 50);
float gh = gui.slider("grid height", 800);
float r = gui.slider("moon radius", 20);
// The time interval between a full moon and the next repetition of the same phase, a synodic month, averages about 29.53 days. -> https://en.wikipedia.org/wiki/Full_moon
float irlPhasePerDay = 1 / 29.53f; // 1 / 29.53 is 0.0338638672536404
float phasePerDay = gui.slider("phasePerDay", irlPhasePerDay); //
int rows = gui.sliderInt("month count", 12);
push();
font();
gui.pushFolder("months");
PVector commonMonthNameTextTranslate = gui.plotXY("month text offset", 700);
PVector commonDayIndexTextTranslate = gui.plotXY("day text offset", 700);
int moonIndex = 0;
for (int yi = 0; yi < rows; yi++) {
String monthFolderName = "for each month/" + (yi + 1);
gui.pushFolder(monthFolderName);
String monthName = gui.text("month name", monthNames[yi].substring(0, 3));
int cols = gui.sliderInt("days", 30);
float yn = norm(yi, 0, rows-1);
float y = map(yn, 0, 1, -gh*0.5f, gh*0.5f);
PVector pos = gui.plotXY("month name pos");
gui.popFolder();
push();
translate(0, y);
textAlign(LEFT, CENTER);
text(monthName, commonMonthNameTextTranslate.x + pos.x, commonMonthNameTextTranslate.y + pos.y);
for (int xi = 0; xi < cols; xi++) {
moonIndex++;
float x = gridPosX + gridStepX * xi; // left aligned columns
float phaseOffset = moonIndex * phasePerDay * TAU;
float todayPhase = (basePhase + phaseOffset) % TAU;
push();
translate(x, 0);
drawMoonPhase(r, todayPhase);
if(yi == 0){
textAlign(CENTER, CENTER);
text(xi + 1, commonDayIndexTextTranslate.x, commonDayIndexTextTranslate.y);
}
pop();
}
pop();
}
gui.popFolder();
pop();
Utils.record(this, gui); // Requires Utils.pde as a separate tab
}
void drawMoonPhase(float r, float phase) {
float lightPhase = (phase + HALF_PI);
boolean growing = cos(lightPhase) < 0;
float angle0 = phase;
float edge0 = cos(angle0);
float angle1 = phase + PI;
float edge1 = cos(angle1);
int fg = gui.colorPicker("foreground", color(0.6)).hex;
int bg = gui.colorPicker("background", color(0.2)).hex;
int outline = gui.colorPicker("outline", color(0.6)).hex;
push();
translate(width/2, height/2);
translate(gui.plotXY("moon pos").x, gui.plotXY("moon pos").y);
fill(growing ? bg : fg);
circle(0, 0, r*2);
fill(!growing ? bg : fg);
noStroke();
float outlineWeight = gui.slider("outline weight", 1.99);
float rInner = r - outlineWeight * 0.5;
circleArc(growing ? rInner*edge0 : rInner*edge1, rInner);
stroke(outline);
noFill();
strokeWeight(outlineWeight);
circle(0, 0, r*2);
pop();
}
void circleArc(float w, float h) {
int detail = gui.sliderInt("detail", 30);
beginShape();
// outer circle
for (int i = 0; i < detail; i++) {
float n = map(i, 0, detail-1, -HALF_PI, HALF_PI);
float x = h * cos(n);
float y = h * sin(n);
vertex(x, y);
}
// inner arc
for (int i = 0; i < detail; i++) {
float n = - map(i, 0, detail-1, -HALF_PI, HALF_PI);
float x = w * cos(n);
float y = h * sin(n);
vertex(x, y);
}
endShape();
}
PFont selectedFont;
void font() {
gui.pushFolder("font");
fill(gui.colorPicker("fill", color(1)).hex);
int size = gui.sliderInt("size", 64, 1, 256);
float leading = gui.slider("leading", 64);
String fontName = gui.text("font name", "Arial").trim();
if (gui.button("list fonts")) {
String[] fonts = PFont.list();
for (String font : fonts) {
println(font + " "); // some spaces to avoid copying newlines from the console
}
}
if (selectedFont == null || gui.hasChanged("font name") || gui.hasChanged("size")) {
selectedFont = createFont(fontName, size);
}
textFont(selectedFont);
textLeading(leading);
gui.popFolder();
}
import java.util.*;
import java.nio.file.Paths;
import java.awt.*;
import java.io.File;
import java.io.IOException;
static class Utils {
private static int recStarted = -1;
private static int saveIndex = 1;
private static String recordingId = generateRandomShortId();
public static String generateRandomShortId() {
return UUID.randomUUID().toString().replace("-", "").substring(0, 8);
}
public static void record(PApplet pApplet, LazyGui gui) {
gui.sliderInt("rec/frame");
gui.sliderSet("rec/frame", saveIndex);
int recLengthDefault = 360;
int recLength = gui.sliderInt("rec/length", recLengthDefault);
boolean recordingInProgress = recStarted != -1 && pApplet.frameCount < recStarted + recLength;
boolean recordingJustEnded = recStarted != -1 && pApplet.frameCount == recStarted + recLength;
if (gui.button("rec/start (ctrl + k)") ||
(Input.getCode(CONTROL).down && Input.getChar('k').pressed)) {
recordingId = generateRandomShortId();
recStarted = pApplet.frameCount;
saveIndex = 1;
gui.sliderSet("rec/frame", saveIndex);
}
boolean stopCommand = gui.button("rec/stop (ctrl + l)");
if (stopCommand ||
(Input.getCode(CONTROL).down && Input.getChar('l').pressed)) {
recStarted = -1;
saveIndex = 1;
recordingJustEnded = true;
gui.sliderSet("rec/frame", saveIndex);
}
String sketchMainClassName = pApplet.getClass().getSimpleName();
String recDir = pApplet.dataPath("video/" + sketchMainClassName + "_" + recordingId);
String recDirAbsolute = Paths.get(recDir).toAbsolutePath().toString();
if (gui.button("rec/open folder")) {
Desktop desktop = Desktop.getDesktop();
File dir = new File(pApplet.dataPath("video"));
if (!dir.exists()) {
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
}
try {
desktop.open(dir);
}
catch (IOException e) {
e.printStackTrace();
}
}
PVector recordRectPos = PVector.add(gui.plotXY("rec/rect pos"), new PVector(pApplet.width / 2f, pApplet.height / 2f));
PVector recordRectSize = gui.plotXY("rec/rect size", pApplet.width, pApplet.height);
int recordRectSizeX = floor(recordRectSize.x);
int recordRectSizeY = floor(recordRectSize.y);
// prevent resolutions odd numbers because ffmpeg can't work with them
if (recordRectSizeX % 2 != 0) {
recordRectSizeX += 1;
}
if (recordRectSizeY % 2 != 0) {
recordRectSizeY += 1;
}
String recImageFormat = ".jpg";
if (recordingInProgress) {
println("saved " + saveIndex + " / " + recLength);
PImage cutout = pApplet.get(
floor(recordRectPos.x) - recordRectSizeX / 2,
floor(recordRectPos.y) - recordRectSizeY / 2,
recordRectSizeX,
recordRectSizeY
);
cutout.save(recDir + "/" + saveIndex++ + recImageFormat);
}
if (gui.toggle("rec/show rect")) {
pApplet.pushStyle();
pApplet.stroke(pApplet.color(0xA0FFFFFF));
pApplet.noFill();
pApplet.rectMode(CENTER);
pApplet.rect(recordRectPos.x, recordRectPos.y, recordRectSizeX, recordRectSizeY);
pApplet.popStyle();
}
int ffmpegFramerate = gui.sliderInt("rec/ffmpeg fps", 60, 1, Integer.MAX_VALUE);
if (gui.toggle("rec/ffmpeg", true) && recordingJustEnded) {
String outMovieFilename = pApplet.dataPath("video/" + sketchMainClassName + "_" + recordingId);
String inputFormat = recDirAbsolute + "/%01d" + recImageFormat;
String command = String.format("ffmpeg -r %s -i %s -start_number_range 100 -an %s.mp4",
ffmpegFramerate, inputFormat, outMovieFilename);
println("running ffmpeg: " + command);
try {
Process proc = Runtime.getRuntime().exec(command);
new Thread(() -> {
Scanner sc = new Scanner(proc.getErrorStream());
while (sc.hasNextLine()) {
println(sc.nextLine());
}
println("finished recording " + outMovieFilename);
}
).start();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
@KrabCode
Copy link
Author

KrabCode commented Nov 8, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment