Skip to content

Instantly share code, notes, and snippets.

@Chlumsky
Created December 29, 2020 00:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Chlumsky/5e88ddfa43d4552afc970a64f6ec97a1 to your computer and use it in GitHub Desktop.
Save Chlumsky/5e88ddfa43d4552afc970a64f6ec97a1 to your computer and use it in GitHub Desktop.
Aztec Diamond Smooth Animation
#if !defined(SHADRON_VERSION) || SHADRON_VERSION < 140
#error This script requires Shadron 1.4 or later
#endif
#include <rng>
#include <shapes>
#include <linearstep>
////////////////////////////////////////////////////////////////
// Aztec diamond logic
comment "Rewind after changing the size!";
param int diamondSize = 128 : logrange(1, 4096);
param float seed;
param bool determinism = true;
const int NO_OPERATION = 0;
const int GENERATE_OPERATION = 1;
const int SHIFT_OPERATION = 2;
const vec4 BLANK = vec4(0.0, 0.0, 0.0, 1.0);
const vec4 KERNEL = vec4(1.0, 0.0, 0.0, 1.0);
const vec4 LEFT = vec4(0.0, 1.0, 0.0, 1.0);
const vec4 RIGHT = vec4(1.0, 1.0, 0.0, 1.0);
const vec4 DOWN = vec4(0.0, 0.0, 1.0, 1.0);
const vec4 UP = vec4(1.0, 0.0, 1.0, 1.0);
bool isEmpty(vec4 texel) { return texel.g+texel.b < 0.5; } // blank or kernel
bool isKernel(vec4 texel) { return texel.g+texel.b < 0.5 && texel.r > 0.5; }
bool isLeft(vec4 texel) { return texel.g-texel.b > 0.5 && texel.r < 0.5; }
bool isRight(vec4 texel) { return texel.g-texel.b > 0.5 && texel.r > 0.5; }
bool isDown(vec4 texel) { return texel.b-texel.g > 0.5 && texel.r < 0.5; }
bool isUp(vec4 texel) { return texel.b-texel.g > 0.5 && texel.r > 0.5; }
vec2 getDirection(vec4 texel) {
return (2.0*texel.r-1.0)*texel.gb;
}
ivec2 centerCoord(vec2 texCoord) {
return ivec2(vec2(shadron_Dimensions)*texCoord)-shadron_Dimensions/2;
}
bool withinDiamond(ivec2 coord, int size) {
return abs(float(coord.x)+0.5)+abs(float(coord.y)+0.5) < float(size)+0.5;
}
bool canBeKernel(ivec2 coord, int size) {
return (coord.x+coord.y+size&1) == 1 && withinDiamond(coord, size) && withinDiamond(coord+1, size);
}
bool isNew(vec4 texel) {
float a = texel.a;
if (a < 0.625)
a *= 2.0;
return a < 0.9375;
}
bool cointoss(ivec2 position, int size) {
RNG rng = rngInitialize(seed);
rng = rngPerturb(rng, size);
rng = rngPerturb(rng, position.x);
rng = rngPerturb(rng, position.y);
if (!determinism)
rng = rngPerturb(rng, shadron_DeltaTime);
return rngGetInt(rng, 2) == 1;
}
vec4 diamondInit(vec2 pos) {
ivec2 coord = centerCoord(pos);
return canBeKernel(coord, 1) ? KERNEL : BLANK;
}
vec4 diamondGen(sampler2D self, vec2 pos, int operation, int size) {
vec4 output = BLANK;
ivec2 coord = centerCoord(pos);
vec4 local = texture(self, pos);
if (operation == NO_OPERATION) {
output = local;
} else {
vec4 local = texture(self, pos);
vec4 left = texture(self, pos+vec2(-1.0, 0.0)*shadron_PixelSize);
vec4 right = texture(self, pos+vec2(+1.0, 0.0)*shadron_PixelSize);
vec4 bottom = texture(self, pos+vec2(0.0, -1.0)*shadron_PixelSize);
vec4 top = texture(self, pos+vec2(0.0, +1.0)*shadron_PixelSize);
if (operation == GENERATE_OPERATION) {
if (isEmpty(local)) {
vec4 bottomLeft = texture(self, pos+vec2(-1.0, -1.0)*shadron_PixelSize);
vec4 bottomRight = texture(self, pos+vec2(+1.0, -1.0)*shadron_PixelSize);
vec4 topLeft = texture(self, pos+vec2(-1.0, +1.0)*shadron_PixelSize);
vec4 topRight = texture(self, pos+vec2(+1.0, +1.0)*shadron_PixelSize);
vec4 horizontal = BLANK, vertical = BLANK;
ivec2 kernelCoord = ivec2(0);
if (isKernel(local) && isEmpty(right) && isEmpty(top) && isEmpty(topRight)) horizontal = LEFT, vertical = DOWN, kernelCoord = coord;
if (isKernel(left) && isEmpty(local) && isEmpty(topLeft) && isEmpty(top)) horizontal = RIGHT, vertical = DOWN, kernelCoord = coord-ivec2(1, 0);
if (isKernel(bottom) && isEmpty(bottomRight) && isEmpty(local) && isEmpty(right)) horizontal = LEFT, vertical = UP, kernelCoord = coord-ivec2(0, 1);
if (isKernel(bottomLeft) && isEmpty(bottom) && isEmpty(left) && isEmpty(local)) horizontal = RIGHT, vertical = UP, kernelCoord = coord-1;
if (!isEmpty(horizontal)) { // if any of the four previous conditions applies
output = cointoss(kernelCoord, size) ? horizontal : vertical;
output.a = kernelCoord == coord ? 0.75 : 0.875; // mark new
}
} else
output = local;
} else if (operation == SHIFT_OPERATION) {
if (isLeft(right) && !isRight(local)) output = LEFT;
if (isRight(left) && !isLeft(local)) output = RIGHT;
if (isDown(top) && !isUp(local)) output = DOWN;
if (isUp(bottom) && !isDown(local)) output = UP;
if (isEmpty(output) && withinDiamond(coord, size)) {
if (canBeKernel(coord, size) && !(isRight(local) && isLeft(right)) && !(isUp(local) && isDown(top)))
output = KERNEL;
}
}
}
return output;
}
////////////////////////////////////////////////////////////////
// Animation logic
param float generateAnimationDuration = 6 : range(8);
param float shiftAnimationDuration = 1.5 : range(4);
param float speedUpOverTime = 0.002 : range(0.05);
var float scale;
var float scaleVector;
watch var int size;
var int operation;
watch var int animationType;
var float animationTimer;
event initialize() {
size = 1;
scale = 0.5*float(diamondSize);
scaleVector = 0.0;
operation = SHIFT_OPERATION;
animationType = GENERATE_OPERATION;
animationTimer = 0.0;
}
event update(float deltaTime) {
deltaTime *= exp(speedUpOverTime*shadron_Time);
float targetScale = float(diamondSize)/float(size+3);
scale += deltaTime*scaleVector;
scaleVector += 4.0*deltaTime*(0.125*(targetScale-scale)-scaleVector);
if (operation == SHIFT_OPERATION) {
animationTimer = 0.0;
operation = GENERATE_OPERATION;
return;
}
operation = NO_OPERATION;
if (animationType == GENERATE_OPERATION)
animationTimer += deltaTime/generateAnimationDuration;
if (animationType == SHIFT_OPERATION)
animationTimer += deltaTime/shiftAnimationDuration;
if (animationTimer >= 1.0) {
animationTimer = 0.0;
if (animationType == SHIFT_OPERATION) {
++size;
operation = SHIFT_OPERATION;
}
if (animationType == GENERATE_OPERATION) {
if (size < diamondSize)
animationType = SHIFT_OPERATION;
else
animationType = NO_OPERATION;
} else
animationType = GENERATE_OPERATION;
}
}
// Animations
float pieceOrd(ivec2 diamondCoord) {
vec2 coord = vec2(diamondCoord)+0.5;
vec2 baseDataCoord = 0.5*coord/float(diamondSize);
vec2 pos = scale*(baseDataCoord-0.5)/vec2(shadron_Aspect, 1.0)+0.5;
return linearstep(0.25, 0.75, mix(pos.x, pos.y, 0.75));
}
float animatePopIn(vec2 screenPos, ivec2 diamondCoord, float start, float end, float screenBased, float diffuse) {
float preT = linearstep(start, end, animationTimer);
float prd = mix(pieceOrd(diamondCoord), mix(screenPos.x, screenPos.y, 0.75), screenBased);
float t = linearstep(mix(prd, 0.0, diffuse), mix(prd, 1.0, diffuse), preT);
float g = 0.0;
float tmo = t-1.0;
return ((g+1.0)*tmo+g)*tmo*tmo+1.0;
}
float animateShift() {
return smoothstep(0.0, 1.0, smoothstep(0.0, 0.95, animationTimer));
}
float animateInflate(vec2 screenPos, ivec2 diamondCoord) { return animatePopIn(screenPos, diamondCoord, 0.0, 0.375, 0.5, 0.25); }
float animateSplit(vec2 screenPos, ivec2 diamondCoord) { return animatePopIn(screenPos, diamondCoord, 0.25, 0.625, 0.0, 0.25); }
float animateArrowFadeIn(vec2 screenPos, ivec2 diamondCoord) { return animatePopIn(screenPos, diamondCoord, 0.375, 0.75, 0.25, 0.25); }
float animateFadeIn(vec2 screenPos, ivec2 diamondCoord) { return animatePopIn(screenPos, diamondCoord, 0.75, 1.0, 0.25, 0.75); }
////////////////////////////////////////////////////////////////
// Diamond data
vec4 diamondStep(sampler2D self, vec2 pos) {
return diamondGen(self, pos, operation, size);
}
feedback DiamondData = glsl(diamondStep, 2*diamondSize) :
initialize(diamondInit),
filter(nearest),
map(clamp_to_border, BLANK),
hidden;
vec4 addCollisionData(vec2 pos) {
vec4 data = texture(DiamondData, pos);
vec2 direction = getDirection(data);
if (dot(direction, direction) > 0.5) {
vec4 neighbor = texture(DiamondData, pos+shadron_PixelSize*direction);
direction += getDirection(neighbor);
if (dot(direction, direction) < 0.5)
data.a *= 0.5;
}
return data;
}
animation EnrichedData = glsl(addCollisionData, sizeof(DiamondData)) :
filter(nearest),
map(clamp_to_border, BLANK),
hidden;
////////////////////////////////////////////////////////////////
// Animation graphics
param float borderThickness = 0.125;
param float arrowSize = 0.25;
vec3 drawDomino(vec2 coord, float pixelSize, vec2 orientation) {
vec3 domino = vec3(0.0); // vec3(fill, border, arrow)
vec2 antiOrientation = vec2(1.0, -1.0)*orientation.yx;
vec2 rt = abs(0.5*orientation+antiOrientation);
vec2 lb = -rt;
float halfBorder = 0.5*borderThickness;
vec2 arrowCoord = mat2(orientation, antiOrientation)*coord;
domino[2] = (
halfPlaneSmooth(arrowCoord, arrowSize*vec2(1.0, 0.0), arrowSize*vec2(0.0, 1.0), pixelSize)*
halfPlaneSmooth(arrowCoord, arrowSize*vec2(0.0, -1.0), arrowSize*vec2(1.0, 0.0), pixelSize)
);
domino[0] = rectangleSmooth(coord, lb, rt, pixelSize);
domino[1] = rectangleSmooth(coord, lb-halfBorder, rt+halfBorder, pixelSize)-rectangleSmooth(coord, lb+halfBorder, rt-halfBorder, pixelSize);
domino[2] *= halfPlaneSmooth(arrowCoord, arrowSize*vec2(-0.1875, 0.0), arrowSize*vec2(-0.1875, -1.0), pixelSize);
domino[2] = max(domino[2], rectangleSmooth(arrowCoord, arrowSize*vec2(-1.0, -0.5), arrowSize*vec2(0.25, 0.5), pixelSize));
return domino;
}
float drawSeparator(vec2 coord, float pixelSize, vec2 orientation) {
vec2 antiOrientation = vec2(1.0, -1.0)*orientation.yx;
vec2 rt = abs(0.5*borderThickness*orientation+antiOrientation);
vec2 lb = -rt;
float separator = 0.0;
separator = rectangleSmooth(coord, lb, rt, pixelSize);
return separator;
}
float drawKernel(vec2 coord, float pixelSize, vec2 orientation, float inflate, float split) {
if (inflate <= 0.0)
return 0.0;
float kernel = 0.0, separator = 0.0;
float halfBorder = 0.5*borderThickness;
vec2 antiOrientation = vec2(1.0, -1.0)*orientation.yx;
vec2 rt = abs(0.5*borderThickness*orientation+inflate*antiOrientation);
vec2 lb = -rt;
vec2 nrt = abs(0.5*borderThickness*orientation+inflate*(1.0-split)*antiOrientation);
vec2 nlb = -nrt;
kernel = rectangleSmooth(coord, vec2(-inflate*(1.0+halfBorder)), vec2(inflate*(1.0+halfBorder)), pixelSize);
kernel -= rectangleSmooth(coord, vec2(-inflate*(1.0+halfBorder)+borderThickness), vec2(inflate*(1.0+halfBorder)-borderThickness), pixelSize);
separator = rectangleSmooth(coord, lb, rt, pixelSize);
if (split < 1.0)
separator -= rectangleSmooth(coord, nlb, nrt, pixelSize);
return max(kernel, separator);
}
const vec3 leftColor = vec3(1.0, 0.8125, 0.0);
const vec3 rightColor = vec3(1.0, 0.0, 0.25);
const vec3 downColor = vec3(0.0, 0.875, 0.0);
const vec3 upColor = vec3(0.0, 0.5, 1.0);
param bool darkMode;
param bool opaqueBg;
vec4 drawDiamondDomino(vec2 coord, float pixelSize, ivec2 diamondCoord, vec4 data, int size, float shift) {
bool q = (int(diamondCoord.x)+int(diamondCoord.y)+size&1) == 1;
vec2 dominoCoord = coord-vec2(diamondCoord);
vec2 direction = getDirection(data);
vec3 domino = vec3(0.0);
if (dot(direction, direction) > 0.5) {
bool collision = data.a < 0.625;
if (collision)
shift *= 1.0+4.0*pow(animationTimer, 24.0);
dominoCoord -= (0.5+(0.5-float(q))*direction.yx)+shift*direction;
if (!collision || dot(dominoCoord, direction) < 0.5-shift)
domino = drawDomino(dominoCoord, pixelSize, direction);
if (collision && shift <= 1.0)
domino[1] = max(domino[1], drawSeparator(dominoCoord+(shift-0.5)*direction, pixelSize, direction));
}
return vec4(domino[0]*direction, domino[1]-domino[2], max(domino[0], domino[1]));
}
#define EPS 0.001
vec4 dominoView(vec2 pos) {
vec4 composite = vec4(0.0);
vec2 baseDataCoord = (pos-0.5)/scale*vec2(shadron_Aspect, 1.0)+0.5;
vec2 coord = 2.0*float(diamondSize)*baseDataCoord;
if (abs(floor(coord.x+0.5)-coord.x) < EPS || abs(floor(coord.y+0.5)-coord.y) < EPS) { // FIX ARTIFACTS
coord += EPS;
baseDataCoord += EPS/(2.0*float(diamondSize));
}
float pxSize = 2.0*float(diamondSize)*shadron_PixelSize.y/scale;
ivec2 diamondCoord = ivec2(floor(coord));
float shift = animationType == SHIFT_OPERATION ? animateShift() : 0.0;
for (int i = 0; i < 5; ++i)
for (int j = 0; j < 5; ++j) {
ivec2 offset = ivec2(-4+i, -i)+j;
vec2 dataCoord = baseDataCoord+0.5/float(diamondSize)*vec2(offset);
vec4 data = texture(EnrichedData, dataCoord);
vec4 dominoData = data;
if (animationType != SHIFT_OPERATION)
dominoData.a = 1.0;
ivec2 doCoord = diamondCoord+offset;
vec4 domino = drawDiamondDomino(coord, pxSize, doCoord, dominoData, size, shift);
if (animationType == GENERATE_OPERATION) {
if (isNew(data)) {
if ((doCoord.x+doCoord.y+size&1) == 0)
doCoord += ivec2(1.25*getDirection(dominoData)).yx;
float fillOpac = animateFadeIn(pos, doCoord);
float arrowOpac = animateArrowFadeIn(pos, doCoord);
domino.a *= mix(fillOpac, arrowOpac, max(0.0, -domino.b));
domino.rg *= fillOpac;
}
}
domino.b = abs(domino.b);
composite = vec4(composite.rg+domino.rg, max(composite.ba, domino.ba));
}
if (animationType == GENERATE_OPERATION) {
for (int i = -2; i <= 1; ++i)
for (int j = -2; j <= 1; ++j) {
ivec2 offset = ivec2(j, i);
vec2 dataCoord = baseDataCoord+0.5/float(diamondSize)*vec2(offset);
vec4 data = texture(DiamondData, dataCoord);
if (data.a < 0.8125) {
ivec2 doCoord = diamondCoord+offset;
float kernel = drawKernel(coord-1.0-vec2(doCoord), pxSize, data.gb, animateInflate(pos, doCoord), animateSplit(pos, doCoord));
composite.ba = max(composite.ba, vec2(kernel));
}
}
}
vec2 colorDirection = normalize(composite.xy);
if (darkMode)
colorDirection *= 0.8125;
vec4 color = vec4(
mix(
max(0.0, -colorDirection.x)*leftColor+
max(0.0, +colorDirection.x)*rightColor+
max(0.0, -colorDirection.y)*downColor+
max(0.0, +colorDirection.y)*upColor,
vec3(0.9375*float(darkMode)),
min(1.0, composite.b+linearstep(0.1, 0.0, length(composite.xy)))
),
composite.a
);
if (opaqueBg)
color = vec4(mix(vec3(1.0-0.90625*float(darkMode)), color.rgb, color.a), 1.0);
return color;
}
animation AztecDiamondAnimation = glsl(dominoView, 1080) : resizable;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment