turns out to be even simpler for your code, @zranger1
shapetheta = theta + objects[i][6] // shape's angle, so each can be different
px = cos(shapetheta) * (x-objects[i][0]) - sin(shapetheta) * (y-objects[i][1])
py = sin(shapetheta) * (x-objects[i][0]) + cos(shapetheta) * (y-objects[i][1])
d = shapeSdf[objects[i][5]](px,py,objectSize);
You were already doing most of the origin relative math (e.g. x-objects[i][0]), so very easy to add.
Shape code, with shape rotation added
// adds per shape rotation
// Global Variables
var maxObjects = 4;
var numObjects = 4;
export var objectSize = 0.21;
export var speed = 0.18;
var numShapes = 4;
var shapeSdf = array(numShapes)
var shapeCompare = array(2);
var filled = 1;
var lineWidth = 0.04;
var theta;
shapeCompare[0] = (f) => (abs(f) > lineWidth); // unfilled shapes
shapeCompare[1] = (f) => (f > lineWidth); // filled shapes
shapeSdf[0] = circle;
shapeSdf[1] = square;
shapeSdf[2] = triangle;
shapeSdf[3] = hexagon;
// signed distance functions for various shapes, adapted for 2D.
// Math from https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
function circle(x,y,r) {
return hypot(x,y) - r;
}
function square(x,y,size) {
dx = abs(x) - size; d1 = max(dx,0);
dy = abs(y) - size; d2 = max(dy,0);
return min(max(dx, dy), 0.0) + hypot(d1,d2);
}
function triangle(x,y,r) {
return max((abs(x) * 0.866025) - (y * 0.5), y) - r / 2;
}
function hexagon(x,y,r){
x = abs(x); y = abs(y);
return max((x * 0.5 + y * 0.866025),x) - r;
}
// array of object vectors
var objects = array(maxObjects);
// UI
export function sliderSize(v) {
objectSize = 0.4 * v;
}
export function sliderSpeed(v) {
speed = v;
}
export function sliderFilled(v) {
filled = (v >= 0.5);
}
// allocate memory for object vectors
function createObjects() {
for (var i = 0; i < maxObjects; i++) {
objects[i] = array(8);
}
}
// create object vector with a random position, direction, speed, color
function initObjects() {
var hue = random(1);
for (var i = 0; i < numObjects; i++) {
var b = objects[i];
b[0] = random(1); // x pos
b[1] = random(1); // y pos
b[2] = random(0.2); // x velocity
b[3] = random(0.2); // y velocity
b[4] = hue; // color
b[5] = i % numShapes; // shape
b[6] = random(1)*PI2 // rotation angle
hue += 0.619033
}
}
// move objects and bounce them off "walls"
function bounce() {
for (var i = 0; i < numObjects; i++) {
var b = objects[i];
// move object
b[0] += b[2] * speed;
b[1] += b[3] * speed;
// bounce off walls by flipping vector element sign when we hit.
if (b[0] < 0) { b[0] = 0; b[2] = -b[2]; continue; }
if (b[1] < 0) { b[1] = 0; b[3] = -b[3]; continue; }
if (b[0] > 1) { b[0] = 1; b[2] = -b[2]; continue; }
if (b[1] > 1) { b[1] = 1; b[3] = -b[3]; continue; }
}
}
createObjects();
initObjects();
export function beforeRender(delta) {
bounce();
theta = PI2 * time(0.1);
// uncomment the block below to rotate entire scene around its
// center
/*
resetTransform();
translate(-0.5,-0.5);
rotate(theta);
translate(0.5,0.5);
*/
}
export function render2D(index,x,y) {
var d;
var v = 0;
for (var i = 0; i < numObjects; i++) {
shapetheta = theta + objects[i][6]
px = cos(shapetheta) * (x-objects[i][0]) - sin(shapetheta) * (y-objects[i][1])
py = sin(shapetheta) * (x-objects[i][0]) + cos(shapetheta) * (y-objects[i][1])
d = shapeSdf[objects[i][5]](px,py,objectSize);
if (shapeCompare[filled](d)) continue;
v = 1-(d/0.04);
s = 1.5-abs(d)/objectSize
h = objects[i][4]-d;
break;
}
hsv(h, s, v*v*v)
}
added I see your concern about speed vs number of objects
I tweaked the above to cache the sin/cos in the shape pre render, and then just pull it once.
With 8 squares, depending on size/fill, it goes as slow as 12fps or so… probably more way to optimize it, but not bad at all.
Added:
if (abs(sx) > objectSize * 1.1 || abs(sy) > objectSize * 1.1) continue;
more than doubled the framerate by avoiding the check if the point is well outside of the size, which cuts down on scanning every pixel for each shape, even if the shape is well away from the pixel, so there is no way it’s close enough. 25-28fps (varies based on size/filled/etc)
8 squares rotating for FPS purposes, some caching done
// adds per shape rotation
// Global Variables
var maxObjects = 8;
var numObjects = 8;
export var objectSize = 0.21;
export var speed = 0.18;
var numShapes = 4;
var shapeSdf = array(numShapes)
var shapeCompare = array(2);
var filled = 1;
var lineWidth = 0.04;
var theta;
shapeCompare[0] = (f) => (abs(f) > lineWidth); // unfilled shapes
shapeCompare[1] = (f) => (f > lineWidth); // filled shapes
shapeSdf[0] = circle;
shapeSdf[1] = square;
shapeSdf[2] = triangle;
shapeSdf[3] = hexagon;
// signed distance functions for various shapes, adapted for 2D.
// Math from https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
function circle(x,y,r) {
return hypot(x,y) - r;
}
function square(x,y,size) {
dx = abs(x) - size; d1 = max(dx,0);
dy = abs(y) - size; d2 = max(dy,0);
return min(max(dx, dy), 0.0) + hypot(d1,d2);
}
function triangle(x,y,r) {
return max((abs(x) * 0.866025) - (y * 0.5), y) - r / 2;
}
function hexagon(x,y,r){
x = abs(x); y = abs(y);
return max((x * 0.5 + y * 0.866025),x) - r;
}
// array of object vectors
var objects = array(maxObjects);
// UI
export function sliderSize(v) {
objectSize = 0.4 * v;
}
export function sliderSpeed(v) {
speed = v;
}
export function sliderFilled(v) {
filled = (v >= 0.5);
}
// allocate memory for object vectors
function createObjects() {
for (var i = 0; i < maxObjects; i++) {
objects[i] = array(9);
}
}
// create object vector with a random position, direction, speed, color
function initObjects() {
var hue = random(1);
for (var i = 0; i < numObjects; i++) {
var b = objects[i];
b[0] = random(1); // x pos
b[1] = random(1); // y pos
b[2] = random(0.2); // x velocity
b[3] = random(0.2); // y velocity
b[4] = hue; // color
b[5] = 1 // shape
b[6] = random(1)*PI2 // rotation
b[7] = sin(b[6])
b[8] = cos(b[6])
hue += 0.619033
}
}
// move objects and bounce them off "walls"
function bounce() {
for (var i = 0; i < numObjects; i++) {
var b = objects[i];
// move object
b[0] += b[2] * speed;
b[1] += b[3] * speed;
// bounce off walls by flipping vector element sign when we hit.
if (b[0] < 0) { b[0] = 0; b[2] = -b[2]; continue; }
if (b[1] < 0) { b[1] = 0; b[3] = -b[3]; continue; }
if (b[0] > 1) { b[0] = 1; b[2] = -b[2]; continue; }
if (b[1] > 1) { b[1] = 1; b[3] = -b[3]; continue; }
}
}
createObjects();
initObjects();
export function beforeRender(delta) {
bounce();
theta = PI2 * time(0.1);
for (var i = 0; i < numObjects; i++) {
shapetheta = theta + objects[i][6]
objects[i][7] = sin(shapetheta)
objects[i][8] = cos(shapetheta)
}
// uncomment the block below to rotate entire scene around its
// center
/*
resetTransform();
translate(-0.5,-0.5);
rotate(theta);
translate(0.5,0.5);
*/
}
export function render2D(index,x,y) {
var d;
var v = 0;
for (var i = 0; i < numObjects; i++) {
sx = x-objects[i][0]
sy = y-objects[i][1]
if (abs(sx) > objectSize * 1.1 || abs(sy) > objectSize * 1.1) continue;
sinshape = objects[i][7]
cosshape = objects[i][8]
px = cosshape * sx - sinshape * sy
py = sinshape * sx + cosshape * sy
d = shapeSdf[objects[i][5]](px,py,objectSize);
if (shapeCompare[filled](d)) continue;
v = 1-(d/0.04);
s = 1.5-abs(d)/objectSize
h = objects[i][4]-d;
break;
}
hsv(h, s, v*v*v)
}
Follow up, cause my GF came home and immediately proclaimed the running pattern was ‘80s kid show’, so I guess I’d better share the code, and add it to the pattern collectionn.
80s kid show
// 80s kid show, by Scruffynerf, with huge chunks of code by @zranger
// v1.0ish, but certainly hackable for more fun. 8-2021
// Global Variables
var maxObjects = 16;
var objectProperties = 12
export var numObjects = 10;
export var objectSize = 0.21;
export var speed = 0.18;
export var filled = 1;
export var lineWidth = 0.04;
export var bounds = 1;
export var spin = 1;
export var flat = 1;
export var whichShapes = 2;
var old = 2
var theta;
var numShapes = 5;
var shapeSdf = array(numShapes)
var shapeCompare = array(2);
shapeCompare[0] = (f) => (abs(f) > lineWidth); // unfilled shapes
shapeCompare[1] = (f) => (f > lineWidth); // filled shapes
shapeSdf[0] = circle;
shapeSdf[1] = square;
shapeSdf[2] = triangle;
shapeSdf[3] = hexagon;
shapeSdf[4] = hexstar;
// signed distance functions for various shapes, adapted for 2D.
// Math from https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
function circle(x,y,r) {
return hypot(x,y) - r;
}
function square(x,y,size) {
dx = abs(x) - size; d1 = max(dx,0);
dy = abs(y) - size; d2 = max(dy,0);
return min(max(dx, dy), 0.0) + hypot(d1,d2);
}
function triangle(x,y,r) {
return (max((abs(x) * 0.866025) - (y * 0.5), y) - r / 2);
}
function hexagon(x,y,r){
x = abs(x); y = abs(y);
return max((x * 0.5 + y * 0.866025),x) - r;
}
function hexstar(x,y,r) {
x = abs(x); y = abs(y);
// the vector of scary constants is just a grab bag of precalculated values.
// 1.73205 is the square root of 3. 0.866025 is half that
// these constants are used in a lot of hexagonal sdf things
// because on a hex with flat to flat radius of 0.5, the
// distance between the points is 0.5/sqrt(3)
dot = 2 * min(-0.5*x + 0.866025 * y,0);
x -= dot * -0.5; y -= dot * 0.866025;
dot = 2 * min(0.866025*x + -0.5 * y,0);
x -= dot * 0.866025; y -= dot * -0.5;
x -= clamp(x, r * 0.5773502692, r * 1.7320508076);
y -= r;
result = hypot(x,y);
// TODO - could be more efficient here. I know I've got a fast bit masking
// signum function around somewhere.
return (y > 0) ? result : -result;
}
// array of object vectors
var objects = array(maxObjects);
// UI
export function sliderFloaters(v) {
numObjects = floor(maxObjects * v);
}
export function sliderType(v) {
old = whichShapes;
whichShapes = floor((numShapes) * v);
if (old != whichShapes){ initObjects(); }
}
export function sliderSize(v) {
objectSize = 0.4 * v;
}
export function sliderSpeed(v) {
speed = v;
}
export function sliderFilled(v) {
filled = (v >= 0.5);
}
export function sliderLine(v) {
lineWidth = 0.04 + 0.05* v
}
export function sliderCutoff(v) {
bounds = 1 + v
}
export function sliderSpin(v) {
spin = (v >= 0.5);
}
export function sliderFlat(v) {
flat = (v <= 0.5);
}
// allocate memory for object vectors
function createObjects() {
for (var i = 0; i < maxObjects; i++) {
objects[i] = array(objectProperties);
}
}
// create object vector with a random position, direction, speed, color, etc
function initObjects() {
var hue = random(1);
for (var i = 0; i < maxObjects; i++) {
var b = objects[i];
b[0] = random(1); // x pos
b[1] = random(1); // y pos
b[2] = random(0.2); // x velocity
b[3] = random(0.2); // y velocity
b[4] = hue; // color
if (whichShapes == numShapes){
b[5] = floor(random(numShapes)) // random shape mix
} else {
b[5] = whichShapes // specific shape
}
b[6] = random(1)*PI2 // rotation
b[7] = sin(b[6])
b[8] = cos(b[6])
b[9] = random(2)+1 // object variation in size
b[10] = random(1) // timing of growth cycle
b[11] = 1 // current size caching so we save the final size per render
hue += 0.4
}
}
// move objects and bounce them off "walls"
function bounce() {
for (var i = 0; i < numObjects; i++) {
var b = objects[i];
// move object
b[0] += b[2] * speed;
b[1] += b[3] * speed;
// bounce off walls by flipping vector element sign when we hit.
if (b[0] < 0) { b[0] = 0; b[2] = -b[2]; continue; }
if (b[1] < 0) { b[1] = 0; b[3] = -b[3]; continue; }
if (b[0] > 1) { b[0] = 1; b[2] = -b[2]; continue; }
if (b[1] > 1) { b[1] = 1; b[3] = -b[3]; continue; }
}
}
createObjects();
initObjects();
function swaptwo(){
s1 = floor(random(numObjects))
s2 = floor(random(numObjects))
for (i = 0; i < objectProperties; i++){
swap = objects[s1][i]
objects[s1][i] = objects[s2][i]
objects[s2][i] = swap
}
}
export function beforeRender(delta) {
bounce();
theta = PI2 * time(.1);
for (var i = 0; i < numObjects; i++) {
shapetheta = theta + objects[i][6]
objects[i][7] = sin(shapetheta)
objects[i][8] = cos(shapetheta)
objects[i][11] = objects[i][9] * objectSize * (.5+wave(time(objects[i][10])))
}
// swap 2 floaters randomly once in a random while, so the same one isn't always on top
if (random(1) > .98){
swaptwo();
}
if (spin){
resetTransform();
translate(-0.5,-0.5);
rotate(theta);
translate(0.5,0.5);
}
}
export function render2D(index,x,y) {
var d,h,s,v;
for (var i = 0; i < numObjects; i++) {
shapesize = objects[i][11]
sx = x-objects[i][0]
sy = y-objects[i][1]
if (abs(sx) > shapesize * bounds || abs(sy) > shapesize * bounds ) continue;
sinshape = objects[i][7]
cosshape = objects[i][8]
px = cosshape * sx - sinshape * sy
py = sinshape * sx + cosshape * sy
d = shapeSdf[objects[i][5]](px,py,shapesize);
if (shapeCompare[filled](d)) continue;
if (flat){
v = 1;
s = 1;
} else {
v = 1-(1.3*d/lineWidth)
s = 1.5-abs(d)/shapesize*bounds
}
h = objects[i][4];
break;
}
hsv(h, s, v*v)
}
Some notes before I forget:
The Shape slider has a trick to cause a reinit if you slide it. Save the old value before you set the new, and if it’s different after, then the slider was moved, so reinitialize the shapes.
Lots of sliders that are really switches, but no UI yet so it’s a bit messy.
Only 5 shape types so far, more would be nice, maxing the slider makes a random mix instead of one type.
The cutoff adjusts how far the shape looks out for points to include, so if you adjust it, you can make more lumpy shapes (blobs)
More floaters, and less cutoff, is slower but hopefully not too slow
Linewidth is adjustable for some thicker lines.
Flat is more “basic” h,1,1 coloring, while non-flat is more edgy/fill looking. The colors tend to group (adding .4 so it’ll roll every 5 shapes) which I liked the look of, and the color scheme tends to be a nice contrasty mix that way, even though it’s picking a random starting color.
I could add more and more to this… it’s got so much we can do.