Created
December 29, 2020 00:51
-
-
Save Chlumsky/5e88ddfa43d4552afc970a64f6ec97a1 to your computer and use it in GitHub Desktop.
Aztec Diamond Smooth Animation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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