Skip to content

Instantly share code, notes, and snippets.

@eleev
Last active February 8, 2025 17:01
Show Gist options
  • Save eleev/3e3acc546ac814617a7710314abdc686 to your computer and use it in GitHub Desktop.
Save eleev/3e3acc546ac814617a7710314abdc686 to your computer and use it in GitHub Desktop.
Animated Voronoi Flow Gradient
//
// VoronoiFlowGradient and the related sources
// Copyright (c) 2024 Astemir Eleev
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include <metal_stdlib>
using namespace metal;
namespace VoronoiFlowGradient {
/**
* Generates a pseudo-random float2 based on the input float2 coordinates.
*
* @param uv Input coordinate as a float2.
* @return A pseudo-random float2 value.
*/
float2 random(float2 uv) {
// Calculate dot products with specific constants to scramble the input coordinates
uv *= float2(dot(uv, float2(127.1, 311.7)), dot(uv, float2(227.1, 531.7)));
// Return a pseudo-random float2 using trigonometric transformations and fractal operations
return 1. - fract(tan(cos(uv) * 173.7) * 371915.3) * fract(tan(cos(uv) * 173.7) * 371915.3);
}
/**
* Computes a point in the Voronoi diagram adjusted by time.
*
* @param id The cell identifier as a float2.
* @param t The time variable to animate the points.
* @return A float2 representing the point's position.
*/
float2 point(float2 id, float t) {
// Generate a point in the Voronoi cell using randomization and trigonometric functions
return sin(t * (random(id + .5) - .5) + random(id - 20.1) * 8.0) * .5;
}
/**
* Main function to generate a Voronoi flow gradient.
*
* This function is stitchable and designed to create a visually continuous Voronoi pattern.
*
* @param position The position coordinate as float2.
* @param bounds The bounds of the input space as float4 (minX, minY, maxX, maxY).
* @param time The current time used for animating the gradient.
* @param size The scaling factor for the Voronoi cells.
* @param offset The offset to apply over time, as float2.
* @param col1 The first color to blend in the gradient as float4 (R, G, B, A).
* @param col2 The second color to blend in the gradient as float4 (R, G, B, A).
* @param angle1 The first angle parameter for color blending.
* @param angle2 The second angle parameter for color blending.
* @return A half4 (RGBA) value representing the color at the given position.
*/
[[ stitchable ]] half4 main(
float2 position,
float4 bounds,
float time,
float size,
float2 offset,
float4 col1,
float4 col2,
float angle1,
float angle2
) {
// Normalize position based on bounds and adjust for center
float2 uv = (position - .5 * bounds.zw) / bounds.z;
uv.y = 1. - uv.y; // Flip Y coordinate to align with conventional top-down rendering
uv -= 0.2; // Offset to position the pattern
uv *= 0.6; // Scale down the pattern
// Apply animated offset and scale
float2 off = time / offset;
uv += off;
uv *= size;
// Get the fractional and integer parts of the UV coordinate
float2 gv = fract(uv) - .5;
float2 id = floor(uv);
float mindist = 1e9; // Initialize minimum distance as large value
float2 vorv;
// Loop over surrounding cells to find the closest point in the Voronoi diagram
for(float i = -2.; i <= 2.; i++) {
for(float j = -2.; j <= 2.; j++) {
float2 offv = float2(i, j);
float dist = length(gv + point(id + offv, time * 2.) - offv);
if (dist < mindist) {
mindist = dist;
vorv = (id + point(id + offv, time * 2.) + offv) / size - off;
}
}
}
// Blend between two colors based on calculated Voronoi point
half4 col = mix(
half4(col1.x, col1.y, col1.z, col1.a),
half4(col2.x, col2.y, col2.z, col1.a),
clamp(vorv.x * angle1 + vorv.y, angle2, 1.) * .5 + .5
);
return col;
}
}
// MARK: SwiftUI API
/*
NOTE: You only need the following snippet, if you embed the shader and the modifier view into a package.
*/
import SwiftUI
/// The Gradienta Metal shader library.
@available(iOS 17, macOS 14, macCatalyst 17, tvOS 17, visionOS 1, *)
@dynamicMemberLookup
public enum FrameworkShadersLibrary {
/// Returns a new shader function representing the stitchable MSL
/// function called `name` in the Inferno shader library.
///
/// Typically this subscript is used implicitly via the dynamic
/// member syntax, for example:
///
/// ```let fn = Framework.myFunction```
///
/// which creates a reference to the MSL function called
/// `myFunction()`.
public static subscript(dynamicMember name: String) -> ShaderFunction {
ShaderLibrary.bundle(.gradienta)[dynamicMember: name + "::main"]
}
}
extension Bundle {
public static var gradienta: Bundle = .module
}
public struct VoronoiFlowGradient: ShapeStyle, View, Sendable {
private var time: TimeInterval
private var size: CGFloat
private var offset: CGPoint
private var color1: Color
private var color2: Color
private var angle1: CGFloat
private var angle2: CGFloat
public init(
time: TimeInterval,
size: CGFloat = 10,
offset: CGPoint = CGPoint(x: -150, y: 300),
color1: Color = Color(red: 153 / 255, green: 41 / 255, blue: 196 / 255),
color2: Color = Color(red: 21 / 255, green: 241 / 255, blue: 211 / 255),
angle1: CGFloat = 2.1,
angle2: CGFloat = -1.3
) {
self.time = time
self.size = size
self.offset = offset
self.color1 = color1
self.color2 = color2
self.angle1 = angle1
self.angle2 = angle2
}
public func resolve(in environment: EnvironmentValues) -> some ShapeStyle {
FrameworkShadersLibrary.VoronoiFlowGradient(
.boundingRect,
.float(time),
.float(size),
.float2(offset.x, offset.y),
.float4(
color1.components.red,
color1.components.green,
color1.components.blue,
color2.components.alpha
),
.float4(
color2.components.red,
color2.components.green,
color2.components.blue,
color2.components.alpha
),
.float(angle1),
.float(angle2)
)
}
}
/*
Utiilty Extension
*/
extension Color {
var components: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
#if canImport(UIKit)
typealias NativeColor = UIColor
#elseif canImport(AppKit)
typealias NativeColor = NSColor
#endif
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
guard NativeColor(self).getRed(&r, green: &g, blue: &b, alpha: &a) else {
return (0, 0, 0, 0)
}
return (r, g, b, a)
}
}
/*
Usage:
*/
struct ContentView: View {
private var start = Date()
var body: some View {
TimelineView(.animation) { context in
GeometryReader { proxy in
let time = context.date.timeIntervalSince1970 - start.timeIntervalSince1970
VoronoiFlowGradient(
time: time * 0.5,
size: 15,
offset: CGPoint(x: 15, y: 15),
color1: .orange,
color2: .red,
angle1: 3.7,
angle2: -5.5
)
}
}
.ignoresSafeArea()
}
}
#Preview {
ContentView()
}
@Jimw338
Copy link

Jimw338 commented Feb 8, 2025

OS technically also has nothing to do with "dying" (whatever it means) =]

Maybe some new word..

"Mewing, Beta Maxing, Gigachad, Baddie: Parents Are Drowning in New Lingo" (Wall Street Journal, https://www.wsj.com/lifestyle/relationships/mewing-beta-maxing-gigachad-baddie-parents-are-drowning-in-new-lingo-3f0d291b)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment