/*
 * "Kiss from a rose" by Abiram Kanagaratnam (AeroShark333)
 *
 * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
 *
 * Contact: <see bottom of shader>
 */

// Recommended range (9.0 ~ 33.0)
#define FLOWER_QUALITY 15.0

// Ray-marcher options
#define MAX_STEPS 100
#define MIN_DIST 0.0003
#define MAX_DIST 24.0
#define MIN_LIGHTING_DIST 2.0 * MIN_DIST
#define NORMAL_OFFSET 0.01
#define SHADOW_STRENGTH 0.1

// Constants
#define PI 3.14159265359

// (P)RNG
float hash(float x){
    return fract(sin(x*1234.0)*4321.0);
}

// Noise
float value(vec2 xy){
    vec2 f0 = floor(xy);

    float h1 = hash(f0.y);
    float h2 = hash(f0.y + 1.0);

    float f1 = f0.x + 1.0;

    float bl = hash(f0.x + h1);
    float br = hash(f1 + h1);

    float tl = hash(f0.x + h2);
    float tr = hash(f1 + h2);

    vec2 fr = xy - f0;
    fr = (3.0 - 2.0*fr)*fr*fr;

    float b = mix(bl, br, fr.x);
    float t = mix(tl, tr, fr.x);

    return mix(b, t, fr.y);
}

// Rotate
mat2 rotate(float a) {
    float s = sin(a);
    float c = cos(a);
    return mat2(c, -s, s, c);
}

// Plane
float getPlaneDistance(vec3 position){
    return position.y;
}

// Cylinder
float getCylinderDistance(vec3 position, vec3 cylinderLowerPosition, vec3 cylinderUpperPosition, float radius){
    vec3 cylinderVector = cylinderUpperPosition - cylinderLowerPosition;
    vec3 positionVector = position - cylinderLowerPosition;
    
    float innerDistance = dot(cylinderVector, positionVector) / dot(cylinderVector, cylinderVector);
    
    vec3 cylinderPosition = cylinderLowerPosition + innerDistance * cylinderVector;

    float x = length(position-cylinderPosition) - radius;
    float y = (abs(innerDistance-0.5)-0.5)*length(cylinderVector);
    
    float exteriorDistance = length(max(vec2(x,y),0.0));
    float interiorDistance = min(max(x,y), 0.0);
    
    float distance = exteriorDistance + interiorDistance;
    
    return distance;
}

// Stem
float getStemDistance(vec3 position){

    vec3 customPosition = position;
    customPosition.x += 0.05* sin(2.0*position.y);
    
    return getCylinderDistance(customPosition, vec3(0.0, 0.75, 0.0), vec3(0.0, 0.0, 0.0), 0.006);
    
}

// Flower
float getFlowerDistance(vec3 position){
    float dist = 100.0;
    
    // Quality of rose
    float hMax = FLOWER_QUALITY;
    
    float norm = 10.0 / hMax;
    for(float h=0.0; h < hMax; h++){
        vec3 newPosition = position;
        
        // More dense in center
        float h2 = (h / hMax);
        h2 *= h2 * hMax * norm;
        
        // Make sure flower begins at stem
        newPosition.x += 0.05* sin(2.0*0.75);
        
        // Create parabolic displacement in y-direction depending on the radius
        newPosition.y -= mix(0.0,2.0*sin(distance(newPosition.xz, vec2(0.0, 0.0))),sqrt(abs(newPosition.y-0.75)));
     
        // Rotate flower (aka 'cylinder')
        float offset =  h2*1.3; // + iTime*0.3;
        float x = clamp(mod(atan(newPosition.x,newPosition.z) + PI/2.0 + offset, PI), 0.0, PI);
        
        // Creates petal-like effect
        newPosition.xz *= 1.0+0.24* clamp(pow(sin(x),1.2),0.0,1.0);
     
        float top = 0.84;
     
        // Artifacts or something...
        if(position.y < 1.0){
            newPosition.xz *= mix(1.0, 0.75, clamp(abs(position.y-top-0.03),0.0,1.0));
            newPosition.xz *= mix(1.0, 0.45, clamp(abs(position.y-top+0.00),0.0,1.0));
            newPosition.xz *= mix(1.0, 0.15, clamp(abs(position.y-top+0.03),0.0,1.0));
        }
        
        // Vary radius for every flower (aka 'cylinder')
        float customRadius = 0.001 + 0.009*h2;
        customRadius *= 0.01/customRadius + pow(abs((position.y-0.75)/(0.18-0.0057*h2)), 0.3);
        dist = min(dist, 0.2*getCylinderDistance(newPosition, vec3(0.0, top-0.0057*h2, 0.0), vec3(0.0, 0.75, 0.0), customRadius));
    }
    
    // Artifacts due to deforming I guess...
    return 3.9*dist;
    
}

// Distance
float getDistance(vec3 position){
    float dist = 0.0;
    
    dist = getPlaneDistance(position);
    
    dist = min(dist, getStemDistance(position));
    
    dist = min(dist, getFlowerDistance(position));
    
    return dist;
}

// Ray-marcher
float rayMarch(vec3 origin, vec3 direction){
    float distance = 0.0;
    for(int i = 0 ; i< MAX_STEPS; i++){
        vec3 position = origin + direction * distance;
        float step = getDistance(position);
        distance += step;
        if(step < MIN_DIST || distance > MAX_DIST){
            break;
        }
    
    }
    return distance;

}

// Normal
vec3 getNormal(vec3 position){
    vec2 offset = vec2(NORMAL_OFFSET, 0.0);
    
    float distance = getDistance(position);
    
    vec3 normal = vec3(
        distance - getDistance(position-offset.xyy),
        distance - getDistance(position-offset.yxy),
        distance - getDistance(position-offset.yyx));
        
    return normalize(normal);
}

// Lighting
float getLighting(vec3 position){
    vec3 lightPosition = vec3(-3.0, 3.0, 0.0);
    
    //vec3 lightDirection = normalize(lightPosition - position);
    vec3 lightDirection = normalize(lightPosition - vec3(0.0));
    
    vec3 normal = getNormal(position);
    
    
    float diffuse = clamp(dot(lightDirection, normal),
                    0.0,
                    1.0);
                    
    float distance = rayMarch(position + normal * MIN_LIGHTING_DIST, lightDirection);
    
    // Shadow
    if(distance < length (lightPosition - position)){
        diffuse *= 0.45;
    }
    
    // Include ambient lighting
    return max(diffuse, 0.15);
}

// Main loop
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Initial color
    vec3 col = vec3(0.0);

    // Centered UV-coordinates
    vec2 uv = (fragCoord-0.5*iResolution.xy)/iResolution.y;
    
    // Input
    vec2 control = iMouse.xy/iResolution.xy;
    
    // Camera
    vec3 origin = vec3(0.0, 1.0, -3.0);
    
    origin.xz *= rotate(iTime*0.3+(1.0-control.x)*PI*2.0);
    
    origin *= 1.0-0.93*control.y;
    origin.y +=  0.87; // +0.3*control.x;
    
    vec3 direction = normalize(vec3(uv.x, uv.y-0.5, 1.0));
    direction.xz *= rotate(iTime*0.3+(1.0-control.x)*PI*2.0);
    
    float dist = rayMarch(origin, direction);
    
    vec3 position = origin + direction * dist;
    
    float diffuse = getLighting(position);
    
    col = vec3(diffuse);
    
    if(position.y > 0.75){
    
        float yVal = 3.0*4.0*(position.y - 0.75);
        float angleVal = 150.0*dot(getNormal(position),vec3(1.0,0.0,0.0));
        float factor = value(vec2(yVal,angleVal));
        
        col = col * (0.06*factor+1.0) * vec3(1.2,1.2,1.1*sqrt(clamp(distance(position.xz,vec2(-0.05* sin(2.0*0.75),0.0))/0.09,0.0,1.0)));
        
        col = clamp(col, vec3(0.0), vec3(1.0));
    }else if(position.y > MIN_DIST){
        // 0.0 ~ 0.75
        float yVal = position.y / 0.75;
        float angleVal = dot(getNormal(position),vec3(1.0,0.0,0.0));
        float factor = value(9.0*vec2(yVal,angleVal));
        col = col * vec3(0.0,0.45+0.30*sin(factor*PI),0.0);
    }else{
        float factor = value(9.0*position.xz);
        col *= col.x;
        col *= vec3(0.3,1.0-factor*0.1,0.3);
        col *= pow(1.0 - clamp(distance(position.xz,vec2(0.0))/100.0,0.0,1.0),45.0);
    }
    
    if(distance(position.xz,vec2(0.0))>9.0){
        col = vec3(0.0);
    }
    
    fragColor = vec4(col,1.0);
}

// E-mail: abiram26@hotmail.com
