#!/bin/bash
# XScreenSaver, Copyright © 2026 Jamie Zawinski <jwz@jwz.org>
#
# Permission to use, copy, modify, distribute, and sell this software and its
# documentation for any purpose is hereby granted without fee, provided that
# the above copyright notice appear in all copies and that both that
# copyright notice and this permission notice appear in supporting
# documentation.  No representations are made about the suitability of this
# software for any purpose.  It is provided "as is" without express or 
# implied warranty.

PATH="$PATH":"$(dirname "$0")"
exec -a "protophore" \
xshadertoy "$@" \
 --program0 - \
<< "_XSCREENSAVER_EOF_"

// Title:  Protophore
// Author: otaviogood
// URL:    https://www.shadertoy.com/view/XljGDz
// Date:   06-Apr-2015
// Desc:   I put together this fractal and then watched some tutorials on how to light sports cars for photography. I think the key is to get the giant softbox up top with just the right fade at the edges. I also ray march 1 reflection.

/*--------------------------------------------------------------------------------------
License CC0 - http://creativecommons.org/publicdomain/zero/1.0/
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
----------------------------------------------------------------------------------------
^This means do anything you want with this code. Because we are programmers, not lawyers.

-Otavio Good
*/

// Number of times the fractal repeats


	#define RECURSION_LEVELS 4
	float localTime = 0.0;
	float marchCount;
	float PI=3.14159265;
	vec3 saturate(vec3 a) { return clamp(a, 0.0, 1.0); }
	vec2 saturate(vec2 a) { return clamp(a, 0.0, 1.0); }
	float saturate(float a) { return clamp(a, 0.0, 1.0); }
	vec3 RotateX(vec3 v, float rad)
	{
	float cos = cos(rad);
	float sin = sin(rad);
	return vec3(v.x, cos * v.y + sin * v.z, -sin * v.y + cos * v.z);
	}
	vec3 RotateY(vec3 v, float rad)
	{
	float cos = cos(rad);
	float sin = sin(rad);
	return vec3(cos * v.x - sin * v.z, v.y, sin * v.x + cos * v.z);
	}
	vec3 RotateZ(vec3 v, float rad)
	{
	float cos = cos(rad);
	float sin = sin(rad);
	return vec3(cos * v.x + sin * v.y, -sin * v.x + cos * v.y, v.z);
	}
	vec3 GetEnvColor2(vec3 rayDir, vec3 sunDir)
	{
	vec3 final = vec3(1.0) * dot(-rayDir, sunDir) * 0.5 + 0.5;
	final *= 0.125;
	if ((rayDir.y > abs(rayDir.x)*1.0) && (rayDir.y > abs(rayDir.z*0.25))) final = vec3(2.0)*rayDir.y;
	float roundBox = length(max(abs(rayDir.xz/max(0.0,rayDir.y))-vec2(0.9, 4.0),0.0))-0.1;
	final += vec3(0.8)* pow(saturate(1.0 - roundBox*0.5), 6.0);
	final += vec3(8.0,6.0,7.0) * saturate(0.001/(1.0 - abs(rayDir.x)));
	final += vec3(8.0,7.0,6.0) * saturate(0.001/(1.0 - abs(rayDir.z)));
	return vec3(final);
	}
	vec3 camPos = vec3(0.0), camFacing;
	vec3 camLookat=vec3(0,0.0,0);
	float smin( float a, float b, float k )
	{
	float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
	return mix( b, a, h ) - k*h*(1.0-h);
	}
	vec2 matMin(vec2 a, vec2 b)
	{
	if (a.x < b.x) return a;
	else return b;
	}
	float spinTime;
	vec3 diagN = normalize(vec3(-1.0));
	float cut = 0.77;
	float inner = 0.333;
	float outness = 1.414;
	float finWidth;
	float teeth;
	float globalTeeth;
	vec2 sphereIter(vec3 p, float radius, float subA)
	{
	finWidth = 0.1;
	teeth = globalTeeth;
	float blender = 0.25;
	vec2 final = vec2(1000000.0, 0.0);
	for (int i = 0; i < RECURSION_LEVELS; i++)
	{
	#ifdef SPLIT_ANIM
	p = RotateY(p, spinTime*sign(p.y)*0.05/blender);
	#endif
	float d = length(p) - radius*outness;
	#ifdef SPLIT_ANIM
	d = max(d, -(max(length(p) - radius*outness + 0.1, abs(p.y) - finWidth*0.25)));
	#endif
	vec3 corners = abs(p) + diagN * radius;
	float lenCorners = length(corners);
	float subtracter = lenCorners - radius * subA;
	vec3 ap = abs(-p) * 0.7071;
	subtracter = max(subtracter, -(abs(ap.x-ap.y) - finWidth));
	subtracter = max(subtracter, -(abs(ap.y-ap.z) - finWidth));
	subtracter = max(subtracter, -(abs(ap.z-ap.x) - finWidth));
	subtracter = min(subtracter, lenCorners - radius * subA + teeth);
	d = -smin(-d, subtracter, blender);
	final = matMin(final, vec2(d, float(i)));
	#ifndef SPLIT_ANIM
	corners = RotateY(corners, spinTime*0.25/blender);
	#endif
	p = vec3(corners.x, corners.z, -corners.y);
	radius *= inner;
	teeth *= inner;
	finWidth *= inner;
	blender *= inner;
	}
	float d = length(p) - radius*outness;
	final = matMin(final, vec2(d, 6.0));
	return final;
	}
	vec2 DistanceToObject(vec3 p)
	{
	vec2 distMat = sphereIter(p, 5.2 / outness, cut);
	return distMat;
	}
	float SphereIntersect(vec3 pos, vec3 dirVecPLZNormalizeMeFirst, vec3 spherePos, float rad)
	{
	vec3 radialVec = pos - spherePos;
	float b = dot(radialVec, dirVecPLZNormalizeMeFirst);
	float c = dot(radialVec, radialVec) - rad * rad;
	float h = b * b - c;
	if (h < 0.0) return -1.0;
	return -b - sqrt(h);
	}
	void mainImage( out vec4 fragColor, in vec2 fragCoord )
	{
	localTime = iTime - 0.0;
	vec2 uv = fragCoord.xy/iResolution.xy * 2.0 - 1.0;
	float zoom = 1.7;
	uv /= zoom;
	vec3 camUp=vec3(0,1,0);
	camLookat=vec3(0,0.0,0);
	float mx=iMouse.x/iResolution.x*PI*2.0-0.7 + localTime*3.1415 * 0.0625*0.666;
	float my=-iMouse.y/iResolution.y*10.0 - sin(localTime * 0.31)*0.5;
	camPos += vec3(cos(my)*cos(mx),sin(my),cos(my)*sin(mx))*(12.2);
	vec3 camVec=normalize(camLookat - camPos);
	vec3 sideNorm=normalize(cross(camUp, camVec));
	vec3 upNorm=cross(camVec, sideNorm);
	vec3 worldFacing=(camPos + camVec);
	vec3 worldPix = worldFacing + uv.x * sideNorm * (iResolution.x/iResolution.y) + uv.y * upNorm;
	vec3 rayVec = normalize(worldPix - camPos);
	localTime = iTime*0.5;
	float rampStep = min(3.0,max(1.0, abs((fract(localTime)-0.5)*1.0)*8.0))*0.5-0.5;
	rampStep = smoothstep(0.0, 1.0, rampStep);
	float step31 = (max(0.0, (fract(localTime+0.125)-0.25)) - min(0.0,(fract(localTime+0.125)-0.25))*3.0)*0.333;
	spinTime = step31 + localTime;
	globalTeeth = rampStep*0.99;
	cut = max(0.48, min(0.77, localTime));
	vec2 distAndMat = vec2(0.5, 0.0);
	float t = 0.0;
	float maxDepth = 24.0;
	vec3 pos = vec3(0,0,0);
	marchCount = 0.0;
	float hit = SphereIntersect(camPos, rayVec, vec3(0.0), 5.6);
	if (hit >= 0.0)
	{
	t = hit;
	for (int i = 0; i < 290; i++)
	{
	pos = camPos + rayVec * t;
	distAndMat = DistanceToObject(pos);
	t += distAndMat.x * 0.7;
	if ((t > maxDepth) || (abs(distAndMat.x) < 0.0025)) break;
	marchCount+= 1.0;
	}
	}
	else
	{
	t = maxDepth + 1.0;
	distAndMat.x = 1000000.0;
	}
	vec3 sunDir = normalize(vec3(3.93, 10.82, -1.5));
	vec3 finalColor = vec3(0.0);
	if (t <= maxDepth)
	{
	vec3 smallVec = vec3(0.005, 0, 0);
	vec3 normalU = vec3(distAndMat.x - DistanceToObject(pos - smallVec.xyy).x,
	distAndMat.x - DistanceToObject(pos - smallVec.yxy).x,
	distAndMat.x - DistanceToObject(pos - smallVec.yyx).x);
	vec3 normal = normalize(normalU);
	float ambientS = 1.0;
	ambientS *= saturate(DistanceToObject(pos + normal * 0.1).x*10.0);
	ambientS *= saturate(DistanceToObject(pos + normal * 0.2).x*5.0);
	ambientS *= saturate(DistanceToObject(pos + normal * 0.4).x*2.5);
	ambientS *= saturate(DistanceToObject(pos + normal * 0.8).x*1.25);
	float ambient = ambientS * saturate(DistanceToObject(pos + normal * 1.6).x*1.25*0.5);
	ambient *= saturate(DistanceToObject(pos + normal * 3.2).x*1.25*0.25);
	ambient *= saturate(DistanceToObject(pos + normal * 6.4).x*1.25*0.125);
	ambient = max(0.035, pow(ambient, 0.3));
	ambient = saturate(ambient);
	vec3 ref = reflect(rayVec, normal);
	ref = normalize(ref);
	float sunShadow = 1.0;
	float iter = 0.1;
	vec3 nudgePos = pos + normal*0.02;
	for (int i = 0; i < 40; i++)
	{
	float tempDist = DistanceToObject(nudgePos + ref * iter).x;
	sunShadow *= saturate(tempDist*50.0);
	if (tempDist <= 0.0) break;
	iter += max(0.00, tempDist)*1.0;
	if (iter > 4.2) break;
	}
	sunShadow = saturate(sunShadow);
	vec3 texColor;
	texColor = vec3(1.0);
	texColor = vec3(0.85, 0.945 - distAndMat.y * 0.15, 0.93 + distAndMat.y * 0.35)*0.951;
	if (distAndMat.y == 6.0) texColor = vec3(0.91, 0.1, 0.41)*10.5;
	texColor = max(texColor, vec3(0.0));
	texColor *= 0.25;
	vec3 lightColor = vec3(0.0);
	lightColor += vec3(0.1,0.35,0.95) * (normal.y * 0.5 + 0.5) * ambient * 0.2;
	lightColor += vec3(1.0) * ((-normal.y) * 0.5 + 0.5) * ambient * 0.2;
	finalColor = texColor * lightColor;
	vec3 refColor = GetEnvColor2(ref, sunDir)*sunShadow;
	finalColor += refColor * 0.35 * ambient;
	finalColor = mix(vec3(1.0, 0.41, 0.41) + vec3(1.0), finalColor, exp(-t*0.0007));
	}
	else
	{
	finalColor = GetEnvColor2(rayVec, sunDir);
	}
	fragColor = vec4(sqrt(clamp(finalColor, 0.0, 1.0)),1.0);
	}

_XSCREENSAVER_EOF_
