Spend a month making one beautiful thing per day, given a bunch of prompts. A month late, but as they say, ’the second best time is now'.
Let’s do it!
22) Shadows
let gui;
let params = {
sunSize: 10,
sunSpeed: 1,
gravity: 0.1, gravityMin: 0, gravityMax: 2.0, gravityStep: 0.01,
sepForce: 0.2, sepForceMin: 0, sepForceMax: 2.0, sepForceStep: 0.01,
friction: 0.1, frictionMin: 0, frictionMax: 1.0, frictionStep: 0.01,
ballCount: 10,
ballSize: 10,
rays: 100, raysMin: 0, raysMax: 1000, raysStep: 1,
}
let state;
function setup() {
createCanvas(400, 400);
state = {
light: {p: createVector(200, 20)},
balls: [],
}
gui = createGuiPanel('params');
gui.addObject(params);
gui.setPosition(420, 0);
}
function draw() {
// Update
if (params.sunSpeed > 0) {
state.light.p.x = 200 + 200 * cos(frameCount * 1 / (101 - params.sunSpeed));
}
while (state.balls.length < params.ballCount) {
state.balls.push({
id: crypto.randomUUID(),
p: createVector(random(400), 20),
v: createVector(random() - 0.5, random() - 0.5),
});
}
while (state.balls.length > params.ballCount) {
state.balls.shift();
}
for (let ball of state.balls) {
// Gravity
let forces = createVector(0, 0);
forces.y += params.gravity;
// Ball/ball collisions
for (let other of state.balls) {
if (ball.id == other.id) continue;
let offset = p5.Vector.sub(other.p, ball.p);
if (offset.mag() < 1.1 * params.ballSize) {
let force = p5.Vector.normalize(offset);
force.mult(-params.sepForce);
forces.add(force);
}
}
// Walls
if (ball.p.x < params.ballSize || ball.p.x > width - params.ballSize) {
ball.v.x *= -(1 - params.friction);
ball.p.x = max(params.ballSize, min(ball.p.x, width - params.ballSize));
}
if (ball.p.y < params.ballSize || ball.p.y > height - params.ballSize) {
ball.v.y *= -(1 - params.friction);
ball.p.y = max(params.ballSize, min(ball.p.y, height - params.ballSize));
}
// Kinematics
forces.mult(1.0 - params.friction);
ball.v.add(forces);
ball.p.add(ball.v);
}
// Draw
background(0);
// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection.html
stroke("yellow");
noFill();
for (let ray = 0; ray < params.rays; ray++) {
let a = TWO_PI * ray / params.rays;
let D = createVector(cos(a), sin(a));
let min_t;
for (let ball of state.balls) {
let L = p5.Vector.sub(ball.p, state.light.p);
let tca = p5.Vector.dot(L, D);
if (tca < 0) continue;
let d = sqrt(p5.Vector.dot(L, L) - tca * tca);
if (d < 0) continue;
let r = params.ballSize / 2;
let thc = sqrt(r * r - d * d);
let t0 = tca - thc;
let t1 = tca + thc;
if (!min_t || t0 < min_t) min_t = t0;
if (!min_t || t1 < min_t) min_t = t1;
}
line(
state.light.p.x,
state.light.p.y,
state.light.p.x + (min_t || 1000) * D.x,
state.light.p.y + (min_t || 1000) * D.y,
);
}
stroke("black")
fill("red");
circle(state.light.p.x, state.light.p.y, params.sunSize);
stroke("black")
fill("white");
for (let ball of state.balls) {
circle(ball.p.x, ball.p.y, params.ballSize);
}
}
Posts in Genuary 2023:
- Genuary 2023.01: Perfect loop
- Genuary 2023.02: Made in 10 minutes
- Genuary 2023.03: Glitch art
- Genuary 2023.04: Intersections
- Genuary 2023.05: Debug view
- Genuary 2023.06: Steal like an artist
- Genuary 2023.07: Sample a color palette
- Genuary 2023.08: Signed Distance Functions
- Genuary 2023.09: Plants
- Genuary 2023.10: Generative Music
- Genuary 2023.11: Suprematism
- Genuary 2023.12: Tessellation
- Genuary 2023.13: Something you've always wanted to learn
- Genuary 2023.14: Asemic Writing
- Genuary 2023.15: Sine Waves
- Genuary 2023.16: Reflections of a Reflection
- Genuary 2023.17: A grid inside a grid inside a grid
- Genuary 2023.18: Definitely not a grid
- Genuary 2023.19: Black and white
- Genuary 2023.20: Art Deco
- Genuary 2023.21: Persian Carpet
- Genuary 2023.22: Shadows
- Genuary 2023.23: Moiré
- Genuary 2023.24: Textile
- Genuary 2023.25: Yayoi Kusama
- Genuary 2023.26: My kid could have made that
- Genuary 2023.27: In the style of Hilma Af Klint
- Genuary 2023.28: Generative poetry
- Genuary 2023.29: Maximalism
- Genuary 2023.30: Minimalism
- Genuary 2023.31: Break a previous image