aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2026-10-25 14:52:36 +0530
committerAkshay Nair <phenax5@gmail.com>2026-10-25 14:52:36 +0530
commitd41559270e57752cc16f97f7e820f3781ed03a49 (patch)
treee92d55544f78966bd3283b39269a34fa61fb57d8 /src/main
parent5e51938952b5275debce06e5cee4a5f71eacaeb1 (diff)
downloadart-playground-openrndr-d41559270e57752cc16f97f7e820f3781ed03a49.tar.gz
art-playground-openrndr-d41559270e57752cc16f97f7e820f3781ed03a49.zip
Ice cream sandwichmaster
Diffstat (limited to 'src/main')
-rw-r--r--src/main/kotlin/icecream/IcecreamProgram.kt48
-rw-r--r--src/main/kotlin/phenax/utils/program.kt6
-rw-r--r--src/main/resources/shaders/icecream.glsl268
3 files changed, 319 insertions, 3 deletions
diff --git a/src/main/kotlin/icecream/IcecreamProgram.kt b/src/main/kotlin/icecream/IcecreamProgram.kt
new file mode 100644
index 0000000..69e8297
--- /dev/null
+++ b/src/main/kotlin/icecream/IcecreamProgram.kt
@@ -0,0 +1,48 @@
+package icecream
+
+import org.openrndr.application
+import org.openrndr.color.*
+import org.openrndr.draw.*
+import org.openrndr.math.IntVector2
+import phenax.utils.*
+
+fun main() = application {
+ recordDuration(20.seconds())
+
+ if (captureEnabled) {
+ configure {
+ width = 1920
+ height = 1440
+ windowResizable = false
+ }
+ } else {
+ configure {
+ width = 640
+ height = 480
+ windowResizable = false
+ }
+ }
+
+ oliveProgram {
+ val enableAntialiasing = captureEnabled
+ val shaderLocalSize = IntVector2(8, 8)
+ val cs by liveComputeShader("icecream")
+
+ val outputBuffer = colorBuffer(width, height, type = ColorType.UINT8)
+
+ // if (captureEnabled)
+ // repeat(1.0) { println("${Math.round((seconds / recordDuration()) * 10000)/100}%") }
+
+ extend {
+ cs.uniform("enable_antialiasing", enableAntialiasing)
+ cs.uniform("seconds", seconds)
+ cs.image("output_image", 0, outputBuffer.imageBinding(0, ImageAccess.WRITE))
+ cs.execute(
+ (outputBuffer.width + shaderLocalSize.x - 1) / shaderLocalSize.x,
+ (outputBuffer.height + shaderLocalSize.y - 1) / shaderLocalSize.y,
+ 1
+ )
+ drawer.image(outputBuffer, 0.0, 0.0, width.toDouble(), height.toDouble())
+ }
+ }
+}
diff --git a/src/main/kotlin/phenax/utils/program.kt b/src/main/kotlin/phenax/utils/program.kt
index fdb6197..821324a 100644
--- a/src/main/kotlin/phenax/utils/program.kt
+++ b/src/main/kotlin/phenax/utils/program.kt
@@ -11,11 +11,11 @@ import org.openrndr.extra.olive.oliveProgram as oliveProgramOriginal
fun Number.seconds(): Number = this
-fun recordDuration(millis: Number) {
- System.setProperty("phenax.record.recordDuration", millis.toDouble().toString())
+fun recordDuration(seconds: Number) {
+ System.setProperty("phenax.record.recordDuration", seconds.toDouble().toString())
}
-fun recordDuration(): Double = System.getProperty("phenax.record.recordDuration")?.toDoubleOrNull() ?: 5000.0
+fun recordDuration(): Double = System.getProperty("phenax.record.recordDuration")?.toDoubleOrNull() ?: 5.0
val recordingEnabled: Boolean by lazy { System.getenv("PHENAX_RECORD") != null }
val screenshotEnabled: Boolean by lazy { System.getenv("PHENAX_SCREENSHOT") != null }
diff --git a/src/main/resources/shaders/icecream.glsl b/src/main/resources/shaders/icecream.glsl
new file mode 100644
index 0000000..38cfb23
--- /dev/null
+++ b/src/main/resources/shaders/icecream.glsl
@@ -0,0 +1,268 @@
+#version 430
+layout(local_size_x = 8, local_size_y = 8) in;
+
+#include <sdf>
+#include <utils>
+#include <noise>
+
+const float MAX_DISTANCE = 1000.; // 1000.
+const int MAX_ITERATIONS = 800; // 600+
+const float MIN_GAP = 0.001;
+
+uniform float seconds = 0;
+layout(rgba8) uniform writeonly image2D output_image;
+
+uniform float antialiasing = 0.28;
+uniform bool enable_antialiasing = false;
+
+uniform vec3 camera_position = vec3(0, 0, -3);
+
+const int O_SANDWICH_FILL = 1;
+const int O_SANDWICH_WAFER = 2;
+
+float time = seconds * 2;
+int break_sym = int(int(time) % 40 == 14 || int(time) % 40 == 18 || int(time) % 40 == 36);
+int break_sym_2 = int(int(time) % 40 == 20 || int(time) % 40 == 3);
+
+float sdf_icecream_sandwich(in vec3 p, in bool show_bite, inout int obj_id) {
+ float fill = sdf_box(p, vec3(0.18, 0.04, 0.1), 0.02);
+ float wafernoise = 0.00062 * noise(p * 900);
+ vec3 wafersize = vec3(0.20, 0.016, 0.12) + wafernoise;
+ float wafer = sdf_box(p - vec3(0, 0.056, 0), wafersize, 0.014);
+ wafer = min(wafer, sdf_box(p - vec3(0, -0.056, 0), wafersize, 0.014));
+
+ {
+ vec3 dotp = p - vec3(0, -0.07, 0);
+ float gap = 0.07;
+ dotp.xz -= round(dotp.xz / gap) * gap;
+ float dots = sdf_circle(dotp, 0.006);
+ wafer = max(wafer, -dots);
+ }
+ {
+ vec3 dotp = p - vec3(0, 0.07, 0);
+ float gap = 0.07;
+ dotp.xz -= round(dotp.xz / gap) * gap;
+ float dots = sdf_circle(dotp, 0.006);
+ wafer = max(wafer, -dots);
+ }
+
+ if (show_bite) {
+ vec3 teethp = p - vec3(-0.19, -0.07, -0.11);
+ float theta = atan(teethp.z, teethp.x);
+ float biteradius = 0.08 + 0.003 * sin(theta * 18);
+ fill = max(fill, -sdf_capsule(teethp, 0.12, biteradius));
+ wafer = max(wafer, -sdf_capsule(teethp, 0.12, biteradius + 8.0 * wafernoise));
+ }
+
+ float dist = min(fill, wafer);
+ if (dist == wafer) obj_id = O_SANDWICH_WAFER;
+ else obj_id = O_SANDWICH_FILL;
+ return dist;
+}
+
+void rotate_all_axes(inout vec3 p, float theta) {
+ mat2x2 rot = mat2x2(cos(theta), -sin(theta), sin(theta), cos(theta));
+ p.xy *= rot;
+ p.yz *= rot;
+ p.xz *= rot;
+}
+
+float scene(in vec3 p, out int obj_id) {
+ float sw;
+ int sw_id;
+ float rotation_time = seconds;
+ {
+ vec3 swp = p;
+ {
+ float theta = rotation_time / 26 + 0.3 * break_sym + 0.17 * break_sym_2;
+ swp.xy *= mat2x2(cos(theta), -sin(theta), sin(theta), cos(theta));
+ }
+ vec2 p1rot, p2rot;
+ float id = rotational_repetition(swp.xy, 20, p1rot, p2rot);
+ int odd = int(id) % 2;
+ vec3 offset = vec3(3.5 - 0.06 * odd * break_sym, 0, 4.5);
+ float theta = -rotation_time / 6 + 0.1 + 0.5 * break_sym;
+ int sw1_id, sw2_id;
+ vec3 p1 = vec3(p1rot, swp.z) - offset;
+ vec3 p2 = vec3(p2rot, swp.z) - offset;
+ rotate_all_axes(p1, theta);
+ rotate_all_axes(p2, theta);
+ float b1 = sdf_icecream_sandwich(p1, bool(break_sym), sw1_id);
+ float b2 = sdf_icecream_sandwich(p2, bool(break_sym), sw2_id);
+ sw = min(b1, b2);
+ if (sw == b1) sw_id = sw1_id;
+ else if (sw == b2) sw_id = sw2_id;
+ }
+ {
+ vec3 swp = p;
+ {
+ float theta = rotation_time / 20 + 0.3 * break_sym;
+ swp.xy *= mat2x2(cos(theta), -sin(theta), sin(theta), cos(theta));
+ }
+ vec2 p1rot, p2rot;
+ float id = rotational_repetition(swp.xy, 20, p1rot, p2rot);
+ int odd = int(id) % 2;
+ vec3 offset = vec3(1 - 0.06 * odd * break_sym, 0, 0);
+ float theta = rotation_time / 4 + 0.3 * break_sym;
+ int sw1_id, sw2_id;
+ vec3 p1 = vec3(p1rot, swp.z) - offset;
+ vec3 p2 = vec3(p2rot, swp.z) - offset;
+ rotate_all_axes(p1, theta);
+ rotate_all_axes(p2, theta);
+ float b1 = sdf_icecream_sandwich(p1, bool(break_sym), sw1_id);
+ float b2 = sdf_icecream_sandwich(p2, bool(break_sym), sw2_id);
+ sw = min(sw, min(b1, b2));
+ if (sw == b1) sw_id = sw1_id;
+ else if (sw == b2) sw_id = sw2_id;
+ }
+ {
+ vec3 swp = p - vec3(0, 0, 0.5);
+ {
+ float theta = rotation_time / 12 + 0.3 * break_sym;
+ swp.xy *= mat2x2(cos(theta), -sin(theta), sin(theta), cos(theta));
+ }
+ vec2 p1rot, p2rot;
+ float id = rotational_repetition(swp.xy, 36, p1rot, p2rot);
+ int odd = int(id) % 2;
+ vec3 offset = vec3(0.8 - 0.06 * odd * break_sym, 0, 0);
+ float theta = -rotation_time / 5 + 0.3 + 0.3 * break_sym;
+ int sw1_id, sw2_id;
+ vec3 p1 = vec3(p1rot, swp.z) - offset;
+ vec3 p2 = vec3(p2rot, swp.z) - offset;
+ rotate_all_axes(p1, theta);
+ rotate_all_axes(p2, theta);
+ float b1 = sdf_icecream_sandwich(p1, bool(break_sym), sw1_id);
+ float b2 = sdf_icecream_sandwich(p2, bool(break_sym), sw2_id);
+ sw = min(sw, min(b1, b2));
+ if (sw == b1) sw_id = sw1_id;
+ else if (sw == b2) sw_id = sw2_id;
+ }
+
+ {
+ vec3 sp = p * 0.8 - vec3(0, 0, -1);
+ sp.yz *= mat2x2(cos(0.42), -sin(0.42), sin(0.42), cos(0.42));
+ sp.xz *= mat2x2(cos(-0.42), -sin(-0.42), sin(-0.42), cos(-0.42));
+ int sw1_id;
+ float sando = sdf_icecream_sandwich(sp, true, sw1_id);
+ sw = min(sw, sando);
+ if (sw == sando) sw_id = sw1_id;
+ }
+
+ float dist = sw;
+ if (dist == sw) obj_id = sw_id;
+ return dist;
+}
+
+void apply_material(
+ in int obj_id,
+ in float dist,
+ inout vec3 color,
+ inout float ambient,
+ inout float shine
+) {
+ if (obj_id == O_SANDWICH_FILL) {
+ color = srgb(1);
+ shine = 2.0;
+ } else if (obj_id == O_SANDWICH_WAFER) {
+ color = srgb(0.44, 0.27, 0.17);
+ shine = 0.06;
+ }
+}
+
+float raymarch_scene(vec3 ray_origin, vec3 ray_direction, out int obj_id) {
+ float dist = 0.;
+ for (int i = 0; i < MAX_ITERATIONS && dist <= MAX_DISTANCE; i++) {
+ vec3 p = ray_origin + ray_direction * dist;
+ float gap = scene(p, obj_id);
+ dist += gap;
+ if (gap <= MIN_GAP)
+ return dist;
+ }
+ obj_id = -1;
+ return dist;
+}
+
+vec3 surface_normal_scene(in vec3 p) {
+ vec2 e = vec2(MIN_GAP, 0);
+ int obj_id;
+ return normalize(vec3(
+ scene(p + e.xyy, obj_id) - scene(p - e.xyy, obj_id),
+ scene(p + e.yxy, obj_id) - scene(p - e.yxy, obj_id),
+ scene(p + e.yyx, obj_id) - scene(p - e.yyx, obj_id)));
+}
+
+vec3 image(vec2 uv) {
+ vec3 campos = camera_position;
+ vec3 uv_direction = normalize(vec3(uv, 1));
+
+ int obj_id = 0;
+ float dist = raymarch_scene(campos, uv_direction, obj_id);
+
+ vec3 p = campos + uv_direction * dist;
+ vec3 norm = surface_normal_scene(p);
+
+ vec3 light = vec3(6, 8, -20);
+ vec3 light_dir = normalize(light - p);
+
+ vec3 c1 = srgb(0.74, 0.93, 0.80);
+ vec3 c2 = srgb(0.98, 0.75, 0.83);
+ vec3 c3 = srgb(0.59, 0.88, 0.94);
+ vec3 c4 = srgb(0.98, 0.78, 0.51);
+
+ vec2 patp = uv * 30;
+ float pat = sin(patp.x) * cos(patp.y - time) - sin(3 * patp.x) - cos(3 * patp.y);
+ vec3 bg_color = c1;
+ bg_color = mix(bg_color, c2, step(pat, 1.0));
+ bg_color = mix(bg_color, c3, step(pat, 0));
+ bg_color = mix(bg_color, c4, step(pat, -1.0));
+ bg_color = mix(bg_color, srgb(0.8, 0, 0), 0.25 * break_sym);
+
+ vec3 color = bg_color;
+ float ambient = 0.05;
+ float shine = 1.;
+ apply_material(obj_id, dist, color, ambient, shine);
+
+ if (obj_id > 0) {
+ float contrast = clamp(dot(norm, light_dir), 0, 1);
+ color *= clamp((1 - ambient) * contrast + ambient, 0, 1);
+
+ vec3 hal = normalize(light_dir - uv_direction);
+ float spec =
+ pow(clamp(dot(norm, hal), 0.0, 1.0), 16.0) * contrast *
+ (0.04 +
+ 0.96 * pow(clamp(1.0 + dot(hal, uv_direction), 0.0, 1.0), 5.0));
+ color += shine * 10. * spec * vec3(1.00, 0.70, 0.5);
+ }
+
+ return color;
+}
+
+vec3 antialiased_image(vec2 uv) {
+ vec2 resolution = vec2(imageSize(output_image).xy);
+ float s = antialiasing / max(resolution.x, resolution.y);
+ vec3 sum = vec3(0);
+ int count = 0;
+ for (int i = -2; i < 2; i++)
+ for (int j = -2; j < 2; j++, count++)
+ sum += image(uv + vec2(i, j) * s);
+ return sum / count;
+}
+
+void main() {
+ vec2 resolution = vec2(imageSize(output_image).xy);
+ float aspect_ratio = resolution.y / resolution.x;
+ vec2 uv = (gl_GlobalInvocationID.xy - resolution / 2) * vec2(1.0, aspect_ratio) / resolution;
+
+ vec3 color = enable_antialiasing ? antialiased_image(uv) : image(uv);
+
+ float t = time;
+ vec2 offset = vec2(mod(t * sin(t * 8 + fbm(vec2(t)) * 0.5), 0.02), 0);
+ if (break_sym == 1) {
+ color.r = (color.r + image(uv + offset).r + image(uv - offset).r) / 3.;
+ color.g = (color.g + image(uv + vec2(offset.y, offset.x)).g + image(uv + offset).g) / 3.;
+ color.b = (color.b + image(uv + vec2(offset.y, offset.x)).b + image(uv - offset).b) / 3.;
+ }
+
+ color = clamp(color, 0, 1);
+ imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(color, 1));
+}