Hi @paulgoux,
I suppose it depends on how complex you want these shapes to be. Maybe signed distance fields would be a topic of interest? You could tailor Processing functions to look more like a shading language, then try porting some simple fragment shaders to a sketch which uses load- and updatePixels. For example, first, to make Processing a bit more like a shading language:
static class Vec2 {
float x = 0.0, y = 0.0;
Vec2(float x, float y) {
this.x = x;
this.y = y;
}
}
Vec2 abs(Vec2 a) {
return new Vec2(abs(a.x), abs(a.y));
}
float clamp(float v, float lb, float ub) {
return min(max(v, lb), ub);
}
float div(float a, float b) {
return b == 0.0 ? 0.0 : a / b;
}
float dot(Vec2 a, Vec2 b) {
return a.x * b.x + a.y * b.y;
}
float length(Vec2 v) {
return sqrt(dot(v, v));
}
Vec2 max(Vec2 a, float b) {
return new Vec2(max(a.x, b), max(a.y, b));
}
float mix(float a, float b, float t) {
return (1.0 - t) * a + t * b;
}
Vec2 mul(Vec2 a, float b) {
return new Vec2(a.x * b, a.y * b);
}
Vec2 sub(Vec2 a, Vec2 b) {
return new Vec2(a.x - b.x, a.y - b.y);
}
Obviously, there are limits to this. For one, custom operators cannot be defined for an object. Functions would have to be rewritten to avoid creating new objects (for the sake of performance).
Then, some signed distance field shape creation functions. I referenced Inigo Quilez when defining these.
/* Cf. http://iquilezles.org/www/articles/distfunctions/distfunctions.htm */
float box(Vec2 p, Vec2 b) {
Vec2 d = sub(abs(p), b);
return min(max(d.x, max(d.y, 0.0)), 0.0) + length(max(d, 0.0));
}
float capsule(Vec2 p, Vec2 a, Vec2 b, float r) {
Vec2 pa = sub(p, a);
Vec2 ba = sub(b, a);
float h = clamp(div(dot(pa, ba), dot(ba, ba)), 0.0, 1.0);
return length(sub(pa, mul(ba, h))) - r;
}
float sphere(Vec2 p, float s) {
return length(p) - s;
}
Last, a main sketch which uses loadPixels and updatePixels:
// For capsule shape.
Vec2 origin = new Vec2(-0.5, -0.5);
Vec2 dest = new Vec2(0.5, 0.5);
float radius = 0.125;
// For box shape.
Vec2 bound = new Vec2(0.325, 0.175);
// For sphere shape.
float size = 0.5;
void setup() {
size(512, 512);
colorMode(RGB, 1.0, 1.0, 1.0, 1.0);
}
void draw() {
// To normalize x and y to [0, 1].
float hNorm = 1.0 / (height - 1.0);
float wNorm = 1.0 / (width - 1.0);
// To animate the mixing of shapes.
Vec2 mouse = new Vec2(mouseX * wNorm, mouseY * hNorm);
loadPixels();
for (int i = 0, y = 0; y < height; ++y) {
// t is y shifted from [0, 1] to [-1, 1].
float t = (y * hNorm) * 2.0 - 1.0;
for (int x = 0; x < width; ++x, ++i) {
// s is x shifted from [0, 1] to [-1, 1].
float s = (x * wNorm) * 2.0 - 1.0;
// Create coordinate from s and t.
Vec2 coord = new Vec2(s, t);
// Create factors from each shape function.
float fac0 = box(coord, bound);
float fac1 = capsule(coord, origin, dest, radius);
float fac2 = sphere(coord, size);
// Combine factors from capsule, box and sphere shape.
float fac = min(fac1, mix(fac0, fac2, mouse.x));
// Wrap the factor to [0, 1].
fac -= floor(fac);
// Convert the factor to a color.
pixels[i] = color(fac, fac, fac, 1.0);
}
}
updatePixels();
}
Long term, however, my guess is that writing a shader would get you closer to the performance you’re looking for.
Best,
Jeremy