Skip to content

Instantly share code, notes, and snippets.

@ArvidSilverlock
Created February 19, 2024 11:44
Show Gist options
  • Select an option

  • Save ArvidSilverlock/4eda3c5c49cdfc619e5e57222230f018 to your computer and use it in GitHub Desktop.

Select an option

Save ArvidSilverlock/4eda3c5c49cdfc619e5e57222230f018 to your computer and use it in GitHub Desktop.
local SIZE = Vector2.new(320, 320)
local SEED = math.random() * 256
local MASK_SCALE = 2^-4
local COLOUR_STEP = 1
local LAYER_STEP = 1
local FRAMES_PER_FRAME = 4
local FRAME_STEP = 1/60
assert(SIZE.X <= 1024 and SIZE.X > 0, "size `x` must be within the range [0, 1024]")
assert(SIZE.Y <= 1024 and SIZE.Y > 0, "size `y` must be within the range [0, 1024]")
-- Maps a [0:0, 1:1] line to a [0:1, 0.5:0, 1:1] line
local function zigzagify(n: number): number
return math.abs(2 * n - 1)
end
-- Perlin noise function
local function noise(x: number, y: number, offset: number)
return math.clamp(math.noise(
x * MASK_SCALE,
y * MASK_SCALE,
SEED + offset * 0.125
) + 0.5, 0, 1)
end
-- Various functions that take a [0, 1] range and spit out a different [0, 1] range
local LAYERS = {
function(n: number)
return n
end,
function(n: number)
if n < 0.2 then
return n / 0.2
elseif n < 0.8 then
return 1 / (1 + math.exp(-8 * (n - 0.5)))
else
return 1 - (n - 0.8) / 0.2
end
end,
function(n: number)
return math.cos(2 * math.pi * n) / 2 + 0.5
end,
function(n: number)
return 1 - n
end,
function(n: number)
if n < 0.2 then
return n / 0.2
elseif n < 0.5 then
return (n - 0.2) / 0.3 + 1
elseif n < 0.8 then
return 1 - (n - 0.5) / 0.3
else
return 0
end
end,
function(n: number)
return math.sin(2 * math.pi * n) / 2 + 0.5
end,
function(n: number)
return ( 2 * n - 1 )^3 / 2 + 0.5
end,
function(n: number)
if n < 0.2 then
return n / 0.2
elseif n < 0.4 then
return 1 - (n - 0.2) / 0.2
elseif n < 0.6 then
return n / 0.2 - 1
elseif n < 0.8 then
return 1 - (n - 0.6) / 0.2
else
return n / 0.2 - 2
end
end,
function(n: number)
return 1 / (1 + math.exp(-8 * (n - 0.5)))
end,
function(n: number)
return 1 / (1 + math.exp(-12 * (n - 0.5)))
end,
function(n: number)
if n < 0.2 then
return n / 0.2
elseif n < 0.5 then
return 1 - (n - 0.2) / 0.3
elseif n < 0.8 then
return (n - 0.5) / 0.3
else
return 1 - (n - 0.8) / 0.2
end
end,
function(n: number)
return 3 * n^2 - 2 * n^3
end,
function(n: number)
if n < 0.2 then
return n / 0.2
elseif n < 0.5 then
return (n - 0.2) / 0.3 + 1
else
return 1 - (n - 0.5) / 0.5
end
end,
function(n: number)
return (1 - n)^2 * 0 + 2 * (1 - n) * n * 0.5 + n^2 * 1
end,
}
-- Initialise the pixel table and mask buffer
local pixels = table.create(SIZE.X * SIZE.Y * 4, 1)
local mask = buffer.create(SIZE.X * SIZE.Y * #LAYERS)
-- How many rows are calculated per frame before we yield
local frameStep = SIZE.X // FRAMES_PER_FRAME
local layerCount = #LAYERS
local area = SIZE.X * SIZE.Y
-- Prepare the noise values in the mask buffer
for x = 0, SIZE.X - 1 do
for y = 0, SIZE.Y - 1 do
for z = 0, layerCount - 1 do
local index = x + ( y * SIZE.X ) + ( z * area )
local noise = noise(x, y, z * 256)
local transform = LAYERS[z + 1]
-- Calculate the value and map it from [0, 1] to [0, 255] to store as an 8 bit integer
local value = math.floor(transform(noise) * 255)
buffer.writeu8(mask, index, value)
end
end
end
local editableImage = Instance.new("EditableImage")
editableImage.Size = SIZE
editableImage.Parent = script.Parent
local t = 0
while true do
-- Calculate the primary and secondary colours based on `t`
local hue = t % COLOUR_STEP / COLOUR_STEP
local primary = Color3.fromHSV(hue, 0.5, 0.8)
local secondary = Color3.fromHSV(hue, 0.125, 1)
-- Calculate which layer we're on, and how far through it we are
local layerIndex = t // LAYER_STEP
local layerLerp = t % LAYER_STEP / LAYER_STEP
-- Calculate the buffer index offsets to get the the necessary layers
local layerOffsetA = ( layerIndex % layerCount ) * area
local layerOffsetB = ( ( layerIndex + 1 ) % layerCount ) * area
for x = 0, SIZE.X - 1 do
for y = 0, SIZE.Y - 1 do
local baseIndex = x + y * SIZE.X
-- Calculate the mask value based on the current and next layers
local a = buffer.readu8(mask, baseIndex + layerOffsetA)
local b = buffer.readu8(mask, baseIndex + layerOffsetB)
local maskValue = ( a + (b - a) * layerLerp ) / 255
-- Offset the mask value by `t`, re-map it to [0:1, 0.5:0, 1:1] and calculate the colour
local shiftedMask = zigzagify(( maskValue + t ) % 1)
local colour = primary:Lerp(secondary, shiftedMask)
-- Add the RGB values to the output, ignore alpha
local index = baseIndex * 4
pixels[index + 1] = colour.R
pixels[index + 2] = colour.G
pixels[index + 3] = colour.B
end
-- Prevent FPS drops
if x % frameStep == 0 then
task.wait()
end
end
-- Manually increment `t` so we don't get jumps from high CPU time
t += FRAME_STEP
-- Write the pixels to the EditableImage
editableImage:WritePixels(Vector2.zero, SIZE, pixels)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment