#version 410


//#pragma include "vertex.glsl"
#pragma include "spect.glsl"
#pragma include "effects.glsl"
#pragma include "colors.glsl"


#define Q0 0.0
#define Q1 HALF_PI
#define Q2 PI
#define Q3 3.0 * HALF_PI


#define MAX_LIGHTS 3


#pragma include "p3d_in.glsl"
#pragma include "p3d_uniform.glsl"
#pragma include "rand.glsl"
#pragma include "simplex3d.glsl"
#pragma include "rot.glsl"


// Uniform inputs
uniform int u_NumberOfLights;
uniform float u_PointSize;

uniform int u_NumPoints;  // Number of points in the instance data buffer
uniform int u_NumFrames;  // Number of frames in the instance data buffer
uniform int u_InstanceCount;  // Number of instances
uniform float u_Scale;
uniform float u_Trans;
uniform vec3 u_VertPositionOffset;
uniform float u_Time;

// Outputs to fragment shader
out vec4 vertColor;
//out vec4 domiPosition;
out vec4 vertPosition;
out vec3 vertNormal;
//out vec3 binormal;
//out vec3 tangent;
out vec4 vertInShadowSpaces[MAX_LIGHTS];
//out vec2 vertMultiTexCoord0;


// Uniform inputs
//uniform sampler1D u_InstanceData;  // Instance data buffer
//uniform samplerBuffer u_InstanceData;  // Instance data buffer
uniform sampler2D u_ROBData;  // Instance data buffer
uniform sampler2D u_SKUData;  // Instance data buffer
uniform sampler2D u_MACData;  // Instance data buffer
uniform sampler2D u_CRAData;  // Instance data buffer
uniform sampler2D u_HANData;  // Instance data buffer
uniform int u_ROBCount;
uniform int u_SKUCount;
uniform int u_MACCount;
uniform int u_CRACount;
uniform int u_HANCount;
uniform int u_Object;   // Object type
uniform int u_Effect;  // Effect type
uniform bool u_Field;  // Field effect
uniform bool u_Move;  // Move effect
uniform int u_Beams;  // Beams effect
uniform bool u_NoMorph;  // No morph effect
uniform bool u_Eight;  // Eight effect
uniform float u_Robot;  // Robot effect
uniform float u_Phase;  // Phase effect
uniform float u_Progress;  // Phase effect
uniform float u_Range;  // Range effect
uniform float u_CloudRadius;
uniform float u_GroupRadius;
uniform float u_GatherFactor;
uniform bool u_Accent;  // Accent effect
uniform int u_InstanceIDStart;  // Instance start index
//uniform int u_InstanceIDStop;  // Instance stop index
uniform int u_GroupCount;
uniform int u_Seed;
uniform float u_FloorLevel;  // Floor level
uniform bool u_UseFloor;  // Use floor effect
uniform bool u_UseGroupOffset;  // No group offset effect
uniform bool u_Colorize;  // Colorize effect
uniform bool u_FirstAccent;  // First accent effect
uniform bool u_Sandbox;
uniform float u_Tongue;
uniform float u_Growth;
uniform int u_Previous;  // Previous object type
uniform float u_BeamProgress;  // Beam progress
uniform bool u_Out;
uniform float u_Morph;
uniform float u_Thunder;
uniform bool u_Pipe;
uniform float u_JointProgress;
uniform float u_Beat0;
uniform float u_Beat1;
uniform float u_Beat2;
uniform float u_Beat3;
uniform float u_Alpha;
uniform float u_Fly;
uniform float u_Hit;
uniform float u_SatRadius;
uniform float u_SatOrbits;
uniform float u_Pal2;
uniform float u_Pal3;
uniform float u_Size;
uniform float u_GeneralAlpha;
out float cloudSpecular;
uniform float u_Explode;

// Globals
int index = gl_InstanceID * 2;
float time = u_Sandbox ? osg_FrameTime : u_Time;
float progress = u_Sandbox ? fract(osg_FrameTime * 0.1) : u_Progress;
int object = u_Sandbox ? int(mod(floor(time * 0.1), 6) + 100) : u_Object;


struct InstanceData {
    vec3 offset;
    vec3 color;
};

struct EffectResult {
    vec3 position;
    vec4 color;
    vec3 offset;
    vec3 angle;
};

InstanceData fetchInstanceData(samplerBuffer u_InstanceData, int index) {
    InstanceData data;
    data.offset = texelFetch(u_InstanceData, index).xyz;
    data.color = texelFetch(u_InstanceData, index + 1).rgb;
    return data;
}

InstanceData fetchInstanceData1D(sampler1D u_InstanceData, int index) {
    InstanceData data;
    data.offset = texelFetch(u_InstanceData, index, 0).xyz;
    data.color = texelFetch(u_InstanceData, index + 1, 0).rgb;
    return data;
}


// Width of a single row in the 2-D texture
const int TEX_WIDTH = 8192;

ivec2 linearToCoord(int idx) {
    // Converts a linear texel index to (x, y) coordinates
    return ivec2(idx % TEX_WIDTH, idx / TEX_WIDTH);
}

InstanceData fetchInstanceData2D(sampler2D u_InstanceData, int index) {
    InstanceData data;

    ivec2 coord0 = linearToCoord(index);       // first texel  (offset.xyz + pad)
    ivec2 coord1 = linearToCoord(index + 1);   // second texel (color.rgb + pad)

    data.offset = texelFetch(u_InstanceData, coord0, 0).xyz;
    data.color  = texelFetch(u_InstanceData, coord1, 0).rgb;

    return data;
}

float softCos(float factor) {
    return cos((factor - 0.5) * TWO_PI) * 0.5 + 0.5;
}


// Radian swirl morhping
vec3 swirl(vec3 offset, float rad_scale, float dir, float off_scale) {
    #define TIME_SCALE 0.05
    vec4 morph;
    morph.x = simplex3d(vec3(+offset.xyz * off_scale) + vec3(0.9, 1.0, 1.1) * time * dir * TIME_SCALE);
    morph.y = simplex3d(vec3(+offset.zxy * off_scale) + vec3(1.1, 0.9, 1.0) * time * dir * TIME_SCALE);
    morph.z = simplex3d(vec3(+offset.yzx * off_scale) + vec3(1.0, 1.1, 0.9) * time * dir * TIME_SCALE);
    morph.w = simplex3d(vec3(-offset.xyz * off_scale) + vec3(1.0, 1.1, 0.9) * time * dir * TIME_SCALE);
    vec3 shift = rot(offset, morph.xyz * PI);
    shift *= (morph.w + 1.0) * rad_scale;
    return shift;
}

vec3 swirl(vec3 offset, float rad_scale) {
    return swirl(offset, rad_scale, 1.0, 0.01);
}


/*
5D wave noise
afl_ext 2018-2022
public domain
*/

float hash(float p){
    return fract(4768.1232345456 * sin(p));
}

float wave(vec4 uv, vec4 emitter, float speed, float phase, float timeshift){
    float dst = distance(uv, emitter);
    return exp(sin(dst * phase - timeshift * speed) - 1.0);
}
vec4 wavedrag(vec4 uv, vec4 emitter){
    return normalize(uv - emitter);
}
float seedWaves = 0.0;
vec4 randWaves(){
    float x = hash(seedWaves);
    seedWaves += 1.0;
    float y = hash(seedWaves);
    seedWaves += 1.0;
    float z = hash(seedWaves);
    seedWaves += 1.0;
    float w = hash(seedWaves);
    seedWaves += 1.0;
    return vec4(x,y,z,w) * 2.0 - 1.0;
}

float getwaves5d(vec4 position, float dragmult, float timeshift){
    float iter = 0.0;
    float phase = 6.0;
    float speed = 2.0;
    float weight = 1.0;
    float w = 0.0;
    float ws = 0.0;
    for(int i=0;i<20;i++){
        vec4 p = randWaves() * 30.0;
        float res = wave(position, p, speed, phase, 0.0 + timeshift);
        float res2 = wave(position, p, speed, phase, 0.006 + timeshift);
        position -= wavedrag(position, p) * (res - res2) * weight * dragmult;
        w += res * weight;
        iter += 12.0;
        ws += weight;
        weight = mix(weight, 0.0, 0.2);
        phase *= 1.2;
        speed *= 1.02;
    }
    return w / ws;
}


int getGroup(int id, int numGroups, float skew) {
    float r = rand(float(id));
    float curved = pow(r, skew);  // zakrzywiamy
    return int(floor(curved * float(numGroups)));
}


vec3 groupColor(int groupID) {
    float r = fract(sin(float(groupID) * 12.9898) * 43758.5453);
    float g = fract(sin(float(groupID) * 78.233) * 12345.6789);
    float b = fract(sin(float(groupID) * 45.164) * 98765.4321);
    return vec3(r, g, b);
}


vec3 generateOffset(int instanceID, float radius, bool randomRadius) {
    // Generate random values: vec2 for static radius, vec3 for random radius
    vec3 randomValues = randomRadius ? irand3(instanceID) : vec3(1.0, irand2(instanceID));

    // Extract parameters for spherical coordinates
    float finalRadius = pow(randomValues.x, 1.0 / 3.0) * radius;       // Randomized or static radius
//    float finalRadius = radius;       // Randomized or static radius
    float azimuth = randomValues.y * TWO_PI;           // Azimuth [0, 2π)
    float cosTheta = randomValues.z * 2.0 - 1.0;       // Cosine of polar angle [-1, 1]
    float theta = acos(cosTheta);                      // Polar angle (θ)

    // Create radial coordinates and convert radial coordinates to Cartesian coordinates
    return cartesian(vec3(finalRadius, azimuth, theta));
}


vec4 generateNest(int instanceID, float radius, bool randomRadius) {
    // Generate random values: vec2 for static radius, vec3 for random radius
    vec3 randomValues = randomRadius ? irand3(instanceID) : vec3(1.0, irand2(instanceID));

    // Extract parameters for spherical coordinates
    float finalRadius = pow(randomValues.x, 1.0 / 3.0) * radius;       // Randomized or static radius
    float azimuth = randomValues.y * TWO_PI;           // Azimuth [0, 2π)
    float cosTheta = randomValues.z * 2.0 - 1.0;       // Cosine of polar angle [-1, 1]
    float theta = acos(cosTheta);                      // Polar angle (θ)

    float nestOut = sin(theta * 20. + azimuth * 1.) * 0.5 + 0.5;
//    nestOut = 0.0;

//    theta += sin(theta * 5.) * 0.5;

    // Create radial coordinates and convert radial coordinates to Cartesian coordinates
    return vec4(cartesian(vec3(finalRadius, azimuth, theta)), nestOut);
}


vec4 generateNest2(int instanceID, float radius, bool randomRadius) {
    // Generate random values: vec2 for static radius, vec3 for random radius
    vec3 randomValues = randomRadius ? irand3(instanceID) : vec3(1.0, irand2(instanceID));

    // Extract parameters for spherical coordinates
    float finalRadius = pow(randomValues.x, 1.0 / 3.0) * radius;       // Randomized or static radius
    float azimuth = randomValues.y * TWO_PI;           // Azimuth [0, 2π)
    float cosTheta = randomValues.z * 2.0 - 1.0;       // Cosine of polar angle [-1, 1]
    float theta = acos(cosTheta);                      // Polar angle (θ)

    float newTheta = theta * 25. + azimuth * 1. + simplex3d(vec3(azimuth, 0, 0)) * 1.0;
    float nestOut = sin(newTheta) * 0.5 + 0.5;
    nestOut *= smoothstep(PI * 1.0, PI * 0.9, theta);  // Smooth transition based on theta

//    if (theta > 2.9) {
//        nestOut = 0.0;  // Disable nest effect for theta < 0.5
//    }
//    nestOut *= smoothstep(0.5, 1.0, theta);
//    nestOut = 0.0;

//    theta += sin(theta * 5.) * 0.5;

    // Create radial coordinates and convert radial coordinates to Cartesian coordinates
    return vec4(cartesian(vec3(finalRadius, azimuth, theta)), nestOut);
}


vec3 pickMainColor(int seed, bool isWarm) {
    int i = seed % 3;

    if (isWarm) {
        if (i == 0) return MAIN_WARM_0;
        else if (i == 1) return MAIN_WARM_1;
        else return MAIN_WARM_2;
    } else {
        if (i == 0) return MAIN_COOL_0;
        else if (i == 1) return MAIN_COOL_1;
        else return MAIN_COOL_2;
    }
}


// ===========================================================
// ========================[ RIN ]============================
// ===========================================================

vec3 ringColor(float random) {
    if (random < 0.01) {
        return AMBER_ORANGE;
    } else {
        return pickMainColor(gl_InstanceID, true);
    }
}


/* ======= geometry ======= */
vec2 figure8_RIN(float r, float sA, float cA)        { return vec2(r * cA,            r * sA * cA); }
vec2 circle_RIN (float r, float sA, float cA)        { return vec2(r * cA,            r * sA); }
vec2 circle2_RIN(float r, float sA, float cA)        { return vec2(r * abs(cA),       r * sA); }

/* ======= warm-paleta ======= */
vec3 pickMainColorWarm_RIN(int seed) {
    int k = seed % 3;
    return mix( mix(MAIN_WARM_0, MAIN_WARM_1, float(k==1)),
                MAIN_WARM_2,                         float(k==2) );
}

/* ======= ring-kolor ======= */
vec3 ringColor_RIN(float rnd, vec3 warm) {                 // branch-less
    return mix(warm, AMBER_ORANGE, step(rnd, 0.01));
}

/* ======= generateOffset (bez bool) ======= */
vec3 generateOffset_RIN(int instanceID, float radius) {
    vec3 r = irand3(instanceID);                    // zawsze losowy promień
    float  finalR  = pow(r.x, 0.3333333) * radius;  // cbrt(x) ~ x^(1/3)
    float  azim    = r.y * TWO_PI;
    float  theta   = acos(r.z * 2.0 - 1.0);
    return cartesian(vec3(finalR, azim, theta));
}


vec3 generateOffsetCart_RIN(int id, float radius)
{
    vec3 r = irand3(id);

    /*  promień: pow(x,⅓) = exp2(log2(x)/3) – na Apple to 2 ALU mniej niż pow()  */
    float finalR = exp2( log2(r.x) * 0.33333333 ) * radius;

    float azim      = r.y * TWO_PI;
    float cosTheta  = r.z * 2.0 - 1.0;
    float sinTheta  = sqrt( max(0.0, 1.0 - cosTheta * cosTheta) );

    /*  jednorazowe sin/cos(azim) – w Apple Metal driverze łączone w 1 instrukcję  */
    vec2 sAz_cAz = sin( vec2(azim, azim + HALF_PI) );
    float sA = sAz_cAz.x;
    float cA = sAz_cAz.y;

    return finalR * vec3(
        sinTheta * cA,
        sinTheta * sA,
        cosTheta
    );
}


/* ======= swirl z ustawionymi stałymi ======= */
vec3 swirl_RIN(vec3 offset) {
    #define TIME_SCALE  0.05
    #define OFF_SCALE   0.01
    #define RAD_SCALE   1.5

    float t = time * TIME_SCALE;
    vec3  p = offset * OFF_SCALE;

    vec3 morph;
    morph.x = simplex3d(p               + vec3(0.9, 1.0, 1.1) * (1.0 + t));
    morph.y = simplex3d(p.zxy           + vec3(1.1, 0.9, 1.0) * (1.0 + t));
    morph.z = simplex3d(p.yzx           + vec3(1.0, 1.1, 0.9) * (1.0 + t));
    float mW = simplex3d(-p             + vec3(1.0, 1.1, 0.9) * (1.0 + t));

    vec3 shifted = rot(offset, morph * PI);
    return shifted * ((mW + 1.0) * RAD_SCALE);
}


EffectResult effectRIN() {

    vec4 r4 = irand4(gl_InstanceID);
    vec2 r2 = irand2(-gl_InstanceID);
    vec3  warmMain = pickMainColorWarm_RIN(gl_InstanceID);

    float alpha = 0.9;
    float growth = u_Growth;

    const float INV_TWO_PI = 0.15915494;          // wklej liczbę, kompilator pomija dzielenie

    float  sTime = sin(time);

    float sR2YTime = sin(r2.y * time);
    float sR2XTime = sin(r2.x * time);

    // Generating object
    mediump vec3 offset;
    float radius;
    const float ARC_K = 0.5;
    mediump float arc = (r4.y * 0.8 + 0.6) * ARC_K * (time + 43758.5453123);
    mediump vec2 scArc = sin(vec2(arc, arc + HALF_PI)); // sin+cos jednym wywołaniem
    mediump float sArc = scArc.x;   // = sin(arc)
    mediump float cArc = scArc.y;   // = cos(arc)
    if (u_Eight) {
        radius = 70;
        offset.xy = figure8_RIN(radius, sArc, cArc);
        float subradius = (r4.x - 0.5) * sR2YTime * 30.;
        vec2 suboffset = circle2_RIN(subradius, sArc, cArc);
        offset.xy += suboffset;
        offset.z = (r4.z - 0.5) * sR2XTime * 30. - sArc * 30. * sTime;
        float m = fract((arc + irand(gl_InstanceID) * 0.5 * HALF_PI) * INV_TWO_PI) * TWO_PI;
        alpha *= step(m, growth * TWO_PI); // =0 gdy warunek nie spełniony
        if (alpha == 0.0) {
            return EffectResult(vec3(0), vec4(0), vec3(0), vec3(0));
        }
    } else {
        radius = 70 + (r4.x - 0.5) * sR2YTime * 30.;
        offset.xy = circle_RIN(radius, sArc, cArc);
        offset.z = (r4.z - 0.5) * sR2XTime * 30.;
    }
    vec3 position = vertPosition.xyz * 2.;
    vec3 color = ringColor_RIN(r4.w, warmMain);

    mediump float absZ = abs(offset.z);
    color += smoothstep(10., 0., absZ) * smoothstep(25.*25., 0., dot(offset.xy, offset.xy)) * AMBER_ORANGE * 50. * smoothstep(0.5, 1.0, growth);

    vec3 cloud = swirl_RIN( generateOffsetCart_RIN(gl_InstanceID, u_CloudRadius) );

    offset = mix(offset, cloud, progress);
    color = mix(color, warmMain, progress);

    // Rotating particles
    vec3 angle = (r4.xyz - 0.5) * time * 5.0;

    return EffectResult(position, vec4(color, alpha), offset, angle);
}



// ===========================================================
// ========================[ OBJ ]============================
// ===========================================================

InstanceData fetchRingData(float random) {
    InstanceData data;
    data.color = ringColor(random);
    return data;
}


EffectResult effectOBJ() {
    #define OBJ_RAD_SCALE 1.0

    float rndNeg = irand(-gl_InstanceID);
    float rndPos = irand( gl_InstanceID);

    InstanceData data;
    switch (object) {
        case MAC: data = fetchInstanceData2D(u_MACData, index); break;
        case ROB: data = fetchInstanceData2D(u_ROBData, index); break;
        case SKU: data = fetchInstanceData2D(u_SKUData, index); break;
        case CRA: data = fetchInstanceData2D(u_CRAData, index); break;
    }

    InstanceData prev;
    switch (u_Previous) {
        case RIN: prev = fetchRingData(rndPos); break;
        case MAC: prev = fetchInstanceData2D(u_MACData, index); break;
        case CRA: prev = fetchInstanceData2D(u_CRAData, index); break;
        case SKU: prev = fetchInstanceData2D(u_SKUData, index); break;
    }

    vec3 offset = data.offset;
    vec3 color = data.color;

    vec3 position = vertPosition.xyz * 1.5;

    // Common
    float pos = -130.0 + progress * 190.0;  // mix(a,b,t) == a + t*(b-a)
    float r2  = 60.0;
    float robot = smoothstep(pos + r2, pos, offset.x);

    // Radian morhping
    float r05 = 15.0;
    vec3 cloud = swirl(generateOffset(gl_InstanceID, 30.0, true), 1.5);
    cloud.z += r05;
    cloud.x += pos;

    float oldRobot = robot;
    float usePrev = step(rndNeg, 0.25);
    robot = mix(robot, 1.0, usePrev);
    color = mix(color, prev.color, usePrev);

    vec3 morphed1 = mix(offset, cloud, robot);  // Old

    float wygibasy2 = smoothstep(0.9, 1.0, oldRobot);
    vec3 morphedSw = (wygibasy2 > 0.0) ? swirl(offset, 1.0) : offset; // OBJ_RAD_SCALE == 1.0
    vec3 morphed2  = mix(offset, morphedSw, wygibasy2);
    offset = mix(morphed1, morphed2, oldRobot);

    cloudSpecular = robot;

    // A bit different
//    vec3 velocity = irand3(-gl_InstanceID);
//    vec3 angle = (velocity - 0.5) * (1.0 - robot) * 15.0 + velocity.zxy * TWO_PI;
//    vec3 vel = irand3(-gl_InstanceID) - 0.5;
//    vec3 angle = vel * ((1.0 - robot) * 15.0) + vel.zxy * TWO_PI + vec3(0.5 * 15.0);
    vec3 vel = irand3(-gl_InstanceID);
    vec3 angle = (vel - 0.5) * ((1.0 - robot) * 15.0) + vel.zxy * TWO_PI;

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}

// ===========================================================
// ========================[ SWA ]============================
// ===========================================================

float impulse(float t, float div) {
    if (t < div) {
        return smoothstep(0.0, div, t);
    } else {
        return smoothstep(1.0, div, t);
    }
//    return t;
}


vec3 move(vec3 offset, vec3 vector, float progress, float cloudRadius) {
//    float progressDelay = progress * 2.0 - length(offset) / u_CloudRadius;
    float progressDelay = progress * 2.0 - length(offset) / cloudRadius;
    float progressPlace = smoothstep(0.0, 1.0, progressDelay);
    return vector * progressPlace;
}


EffectResult effectSWA() {
    // Common
//    #define FIELD true
    #define SWA_RAD_SCALE 1.5

//    vec3 r3 = rand3(gl_InstanceID * 16807.);
    vec3 r3 = irand3(gl_InstanceID);

    float robot;
    if (u_NoMorph) {
        robot = u_Robot;
    } else {
        robot = sin(time * 0.2) * 0.5 + 0.5;
    }

    vec3 offset;

    // Generating object
    offset = generateOffset(gl_InstanceID + 1, u_CloudRadius, true);

    vec3 color = pickMainColor(gl_InstanceID, true);
    vec3 position = vertPosition.xyz;

    // Radian morhping
    robot = 0.0;
    vec3 shift = swirl(offset, SWA_RAD_SCALE * 1.0);
    offset = mix(shift, offset, robot);
    color = mix(vec3(0.50, 0.25, 0.75), color, robot);

    // Moving to other place
    #define POS1 vec3(-20.0, 0.0, 0.0)
    #define POS2 vec3(+40.0, 0.0, 0.0)
    if (u_Move) {
        float progress = fract(time * 0.2);
        offset += POS1 + move(offset, POS2, progress, u_CloudRadius);
    }

    // Beams effect
//    int u_Beams = 40;
    for (int i = 0; i < u_Beams; i++) {
//        vec4 r4 = rand4(float(i) * 16807.);
        vec4 r4 = irand4(i);
        vec3 speed = r4.xyz * 2.0 - 1.0;
        vec3 ray = normalize(vec3(sin(speed * time * 0.1)));
        float t = dot(offset, ray);
        float dist;
        float phase = mod(r4.w * time / 4.0, 1.0) * 2.0;
        if (t > 0.0) {
            vec3 projection = t * ray;
            dist = length(offset - projection);
            float force = smoothstep(10.0, 0.0, dist) * impulse(phase, 0.15);
            offset *= 1.0 + force * 2.0;
            color += vec3(1.0, 0.25, 0.0) * force * 50. * smoothstep(10., 20., length(offset)) * 1.0;
        }
    }

    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;

    if (u_Field) {
        float dist = length(offset - vec3(0.0, 0.0, -40.0));
        if (dist < 40.0) {
            color = vec3(500.0, 100.0, 0.0);
            offset *= 40. / dist;
        }
    }

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ FIE ]============================
// ===========================================================

EffectResult effectFIE() {
    #define SWA_RAD_SCALE 1.5

//    vec3 r3 = rand3(gl_InstanceID * 16807.);
    vec3 r3 = irand3(gl_InstanceID);

    float robot;
    if (u_NoMorph) {
        robot = u_Robot;
    } else {
        robot = sin(time * 0.2) * 0.5 + 0.5;
    }

    vec3 offset;

    // Generating object
    offset = generateOffset(gl_InstanceID + 1, u_CloudRadius, true);

    vec3 color = pickMainColor(gl_InstanceID, false);
    vec3 position = vertPosition.xyz;

    // Radian morhping
    robot = 0.0;
    vec3 shift = swirl(offset, SWA_RAD_SCALE * 1.0);
    offset = mix(shift, offset, robot);
    color = mix(vec3(0.50, 0.25, 0.75), color, robot);

    // Moving to other place
    #define POS1 vec3(-20.0, 0.0, 0.0)
    #define POS2 vec3(+40.0, 0.0, 0.0)
    float progress = sin(time * 0.5) * 0.5 + 0.5;  // Oscillating progress
    progress *= 2.0;
    offset += POS1 + move(offset, POS2, progress, u_CloudRadius);
    offset *= 2.0;


    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;

    float dist = length(offset - vec3(0.0, 0.0, -40.0));
    if (dist < 30.0) {
        color = vec3(500.0, 100.0, 0.0);
        offset *= 30. / dist;
    }

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ CCC ]============================
// ===========================================================

#define CCC_OFF_SCALE 0.12  // 0.05
#define CCC_RAD_SCALE 4.0
#define DISTANCE_SCALE 2.6  // Distance between warm and cool positions

vec3 wind(int instanceID, float satRadius, float satOrbits) {
    vec3 randomValues = vec3(1.0, irand2(instanceID));

    float azimuth = randomValues.y * TWO_PI;           // Azimuth [0, 2π)
    float cosTheta = randomValues.z * 2.0 - 1.0;       // Cosine of polar angle [-1, 1]

    vec3 move;
    move.x = simplex3d(vec3(azimuth * 2., cosTheta * 2, time * 0.2));
    azimuth += move.x * 0.5;
    move.y = simplex3d(vec3(time * 0.2, azimuth * 2., cosTheta * 2.));
    cosTheta += move.y * 0.5;
    cosTheta = (cosTheta < -1.0) ? (1.0 - mod(-cosTheta, 2.0)) : ((cosTheta > 1.0) ? (-1.0 + mod(cosTheta, 2.0)) : cosTheta);
    move.z = simplex3d(vec3(cosTheta * 2., time * 0.2, azimuth * 2.));

    float finalRadius = satRadius + move.z * satOrbits;  // Randomized or static radius

    float theta = acos(cosTheta);                      // Polar angle (θ)

    // Create radial coordinates and convert radial coordinates to Cartesian coordinates
    return cartesian(vec3(finalRadius, azimuth, theta));
}


EffectResult effectCCC() {
    #define POS_WARM vec3(-40.0, 0.0, 0.0)
    #define POS_COOL vec3(+40.0, 0.0, 0.0)

    bool isWarm = (gl_InstanceID % 2 == 0);
    vec3 r3 = irand3(gl_InstanceID);

    float robot;
    if (u_NoMorph) {
        robot = u_Robot;
    } else {
        robot = sin(time * 0.2) * 0.5 + 0.5;
    }

    vec3 offset;

    // Generating object
    offset = generateOffset(gl_InstanceID + 1, u_CloudRadius, true);

//    vec3 colorWarm = pickMainColor(gl_InstanceID, true);
    vec3 colorWarm = BURNT_ORANGE;
    vec3 colorCool = pickMainColor(gl_InstanceID, false);
    vec3 color;

    vec3 position = vertPosition.xyz * u_Size;

    vec3 pos;
    float seed;
    float alpha;
    if (isWarm) {
        pos = POS_WARM;
        seed = 1.0;
        color = colorWarm;
        alpha = u_Alpha;
    } else {
        pos = POS_COOL;
        seed = -1.0;
        color = colorCool;
        alpha = 1.0;
    }


    // Radian morhping
    robot = 0.0;
    vec3 shift = swirl(offset, CCC_RAD_SCALE, seed, CCC_OFF_SCALE);  // last parameter is about offset scale, less == more ball
    offset = mix(shift, offset, robot);

//    progress = 0.53;
//    offset += move(offset, -pos * 2.0, progress * 2.9, u_CloudRadius * 1.0);
    offset += move(offset, -pos * DISTANCE_SCALE, progress, u_CloudRadius * 20.0);

    offset += pos * DISTANCE_SCALE;

    offset = rotX(offset, offset.x * 0.02);

    float coll = smoothstep(50.0, 0.0, length(offset));
    vec3 finalColor = mix(color, (colorWarm + colorCool) * 10., coll);
    finalColor = mix(finalColor, color, u_JointProgress);

    #define COLOR1 vec3(0.09, 0.10, 0.15)  // ciemne tło
    #define COLOR3 vec3(0.83, 0.16, 0.18)  // czerwone punkty
    #define COLOR4 vec3(0.20, 0.46, 0.78)  // niebieskie punkty
    #define COLOR6 vec3(0.42, 0.24, 0.20)  // brązowo-pomarańczowe elementy
//    #define COLOR1 vec3(1.00, 0.00, 0.00)  // ciemne tło
//    #define COLOR3 vec3(0.00, 1.00, 0.00)  // czerwone punkty
//    #define COLOR4 vec3(0.00, 0.00, 1.00)  // niebieskie punkty
//    #define COLOR6 vec3(1.00, 1.00, 0.00)  // brązowo-pomarańczowe elementy

//    //#define COLOR7  vec3(0.85, 0.74, 0.21)  // intensywny żółty
//    #define COLOR7  vec3(226./255., 194./255., 83./255.)  // intensywny żółty
//    //#define COLOR13 vec3(0.45, 0.28, 0.65)  // fioletowo-ultrafioletowy akcent
//    #define COLOR13 vec3(85./255., 23./255., 53./255.)  // fioletowo-ultrafioletowy akcent
//    #define COLOR17 vec3(0.92, 0.76, 0.16)  // żółty
//    #define COLOR18 vec3(0.87, 0.34, 0.14)  // pomarańczowy
//    //#define COLOR19 vec3(0.72, 0.16, 0.29)  // różowo-czerwony
//    #define COLOR19 vec3(116./255., 50./255., 97./255.)  // różowo-czerwony
//    #define COLOR21 vec3(0.33, 0.10, 0.35)  // ciemny fiolet
//    #define COLOR26 vec3(0.80, 0.24, 0.55)  // purpurowy

    #define COLOR7  vec3(0.0, 0.0, 1.0)  // intensywny żółty
    #define COLOR13 vec3(0.0, 1.0, 0.0)  // fioletowo-ultrafioletowy akcent
    #define COLOR17 vec3(0.0, 1.0, 1.0)  // żółty
    #define COLOR18 vec3(1.0, 0.0, 0.0)  // pomarańczowy
    #define COLOR19 vec3(1.0, 0.0, 1.0)  // różowo-czerwony
    #define COLOR21 vec3(1.0, 1.0, 0.0)  // ciemny fiolet
    #define COLOR26 vec3(1.0, 1.0, 1.0)  // purpurowy

    const float colorDistribution[4] = float[4](
        0.15, // COLOR1 - ciemne tło
        0.20, // COLOR3 – czerwone punkty
        0.50, // COLOR4 – niebieskie punkty
        0.15  // COLOR6 – brązowo-pomarańczowe elementy
    );
    float ratio = float(gl_InstanceID) / float(u_InstanceCount);
    float threshold1 = colorDistribution[0];
    float threshold2 = threshold1 + colorDistribution[1];
    float threshold3 = threshold2 + colorDistribution[2];
    vec3 color2;
    if (ratio < threshold1) {
        color2 = COLOR1;
    }
    else if (ratio < threshold2) {
        color2 = COLOR3;
    }
    else if (ratio < threshold3) {
        color2 = COLOR4;
    }
    else {
        color2 = COLOR6;
    }

    // Old version:
//    const float colorDistribution3[7] = float[7](
//        0.15, // COLOR7  – intensywny żółty
//        0.25, // COLOR13 – fioletowo-ultrafioletowy
//        0.05, // COLOR17 – żółty
//        0.30, // COLOR18 – pomarańczowy
//        0.10, // COLOR19 – różowo-czerwony
//        0.05, // COLOR21 – ciemny fiolet
//        0.10  // COLOR26 – purpurowy
//    );
//    float ratio3 = float(gl_InstanceID) / float(u_InstanceCount);
//    float threshold3_1 = colorDistribution3[0];
//    float threshold3_2 = threshold3_1 + colorDistribution3[1];
//    float threshold3_3 = threshold3_2 + colorDistribution3[2];
//    float threshold3_4 = threshold3_3 + colorDistribution3[3];
//    float threshold3_5 = threshold3_4 + colorDistribution3[4];
//    float threshold3_6 = threshold3_5 + colorDistribution3[5];
//    vec3 color3;
//    if (ratio3 < threshold3_1) {
//        color3 = COLOR7;
//    }
//    else if (ratio3 < threshold3_2) {
//        color3 = COLOR13;
//    }
//    else if (ratio3 < threshold3_3) {
//        color3 = COLOR17;
//    }
//    else if (ratio3 < threshold3_4) {
//        color3 = COLOR18;
//    }
//    else if (ratio3 < threshold3_5) {
//        color3 = COLOR19;
//    }
//    else if (ratio3 < threshold3_6) {
//        color3 = COLOR21;
//    }
//    else {
//        color3 = COLOR26;
//    }

    // New version:
    const float colorDistribution3[8] = float[8](
        0.16, // MAIN_WARM_0
        0.16, // MAIN_WARM_1
        0.16, // MAIN_WARM_2
        0.02, // DNA_COL
        0.16, // MAIN_COOL_0
        0.16, // MAIN_COOL_1
        0.16, // MAIN_COOL_2
        0.02  // MIDNIGHT_BLUE
    );
    float ratio3 = float(gl_InstanceID) / float(u_InstanceCount);
    float threshold3a = 0.0;
    vec3 color3;
    if (ratio3 < (threshold3a += colorDistribution3[0])) {
        color3 = MAIN_WARM_0;
    }
    else if (ratio3 < (threshold3a += colorDistribution3[1])) {
        color3 = MAIN_WARM_1;
    }
    else if (ratio3 < (threshold3a += colorDistribution3[2])) {
        color3 = MAIN_WARM_2;
    }
    else if (ratio3 < (threshold3a += colorDistribution3[3])) {
        color3 = DNA_COL;
    }
    else if (ratio3 < (threshold3a += colorDistribution3[4])) {
        color3 = MAIN_COOL_0;
    }
    else if (ratio3 < (threshold3a += colorDistribution3[5])) {
        color3 = MAIN_COOL_1;
    }
    else if (ratio3 < (threshold3a += colorDistribution3[6])) {
        color3 = MAIN_COOL_2;
    }
    else {
        color3 = MIDNIGHT_BLUE;
    }

    finalColor = mix(finalColor, color3, u_Pal2);
    finalColor = mix(finalColor, color2, u_Pal3);

    //    float u_Fly = 1.0;
    //    float u_Hit = 3.3;
    vec3 jointOffset = wind(gl_InstanceID, u_SatRadius, u_SatOrbits);
    offset = mix(offset, jointOffset, u_JointProgress);  // Mix with joint offset
    offset.y += u_Hit * 20.0;  // Flying effect, can be adjusted
    finalColor *= u_Fly * 10. + 1.0;

    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;

    alpha *= u_GeneralAlpha;

//    float u_Explode = abs(sin(osg_FrameTime));
    offset *= 1.0 + u_Explode * 2.0;

//    float dist = length(offset - vec3(0.0, 0.0, -40.0));
//    if (dist < 30.0) {
//        color = vec3(500.0, 100.0, 0.0);
//        offset *= 30. / dist;
//    }

//    cloudSpecular = 1.0;
//    finalColor *= 3.;

    return EffectResult(position, vec4(finalColor, alpha), offset, angle);
}


// ===========================================================
// ========================[ CHI ]============================
// ===========================================================

EffectResult effectCHI() {
    #define SWA_RAD_SCALE 1.5

//    vec3 r3 = rand3(gl_InstanceID * 16807.);
    vec3 r3 = irand3(gl_InstanceID);

    vec3 offset;

    // Generating object
    offset = generateOffset(gl_InstanceID + 1, u_CloudRadius, true);

    vec3 color = pickMainColor(gl_InstanceID, true);
    vec3 position = vertPosition.xyz;

    // Radian morhping
    float robot = 0.0;
    vec3 shift = swirl(offset, SWA_RAD_SCALE * 1.0);
    offset = mix(shift, offset, robot);
    color = mix(vec3(0.50, 0.25, 0.75), color, robot);

    // Moving to other place
//    #define POS1 vec3(-20.0, 0.0, 0.0)
//    #define POS2 vec3(+40.0, 0.0, 0.0)
//    float progress = sin(time * 0.5) * 0.5 + 0.5;  // Oscillating progress
//    progress *= 2.0;
//    offset += POS1 + move(offset, POS2, progress, u_CloudRadius);
//    offset *= 2.0;


    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;

//    float dist = length(offset - vec3(0.0, 0.0, -40.0));
//    if (dist < 30.0) {
//        color = vec3(500.0, 100.0, 0.0);
//        offset *= 30. / dist;
//    }

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ BEA ]============================
// ===========================================================

InstanceData beams(int id) {
    InstanceData data;
    #define SWA_RAD_SCALE 1.5
    vec3 r3 = irand3(id);
    float robot;
    if (u_NoMorph) {
        robot = u_Robot;
    } else {
        robot = sin(time * 0.2) * 0.5 + 0.5;
    }
    // Generating object
    data.offset = generateOffset(id + 1, u_CloudRadius, true);
    data.color = pickMainColor(gl_InstanceID, true);

    // Radian morhping
    vec3 shift = swirl(data.offset, OBJ_RAD_SCALE);
    data.offset = mix(shift, data.offset, robot);
    data.color = mix(RUST_COL, data.color, robot);

    // Beams effect
    for (int i = 0; i < u_Beams; i++) {
        vec4 r4 = irand4(i);
        vec3 speed = r4.xyz * 2.0 - 1.0;
        vec3 ray = normalize(vec3(sin(speed * time * 0.1)));
        float t = dot(data.offset, ray);
        float dist;
//        float phase = mod(r4.w * time / 4.0, 1.0) * 2.0;
        if (t > 0.0) {
            vec3 projection = t * ray;
            dist = length(data.offset - projection);
//            float force = smoothstep(10.0, 0.0, dist) * impulse(phase, 0.15);

            int group = int(floor(float(i) * 4.0 / 40.0)); // 4 grupy z zakresu 0–39
            float beat;
            if (group == 0) {
                beat = u_Beat0;
            } else if (group == 1) {
                beat = u_Beat1;
            } else if (group == 2) {
                beat = u_Beat2;
            } else {
                beat = u_Beat3;
            }

//            beat = 0.4;

            float force = smoothstep(12.0, 0.0, dist) * beat;
            data.offset *= 1.0 + force * 2.0;
            data.color += vec3(1.0, 0.25, 0.0) * force * 30. * smoothstep(10., 20., length(data.offset)) * 1.0;
//            data.color = RUST_COL + vec3(1.0, 0.25, 0.0) * 30. * 0.5 * 0.5;
        }
    }
    data.offset = rot(data.offset, vec3(1.197, 1.141, 1.071) * time);  // Rotating object

    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;
    return data;
}


EffectResult effectBEA() {
    vec3 position = vertPosition.xyz;
    vec3 offset;
    vec3 color;
    vec3 r3 = irand3(gl_InstanceID);

    InstanceData data = beams(gl_InstanceID);
    offset = data.offset;
    color = data.color;

    vec3 angle = (r3 - 0.5) * time * 5.0;  // Rotating particles

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ DIV ]============================
// ===========================================================

EffectResult effectDIV() {
    int pairID = gl_InstanceID / 2;

    vec3 r3 = irand3(pairID);
    vec3 q3 = irand3(gl_InstanceID);

    float phase;

    if (u_Accent) {
        phase = (smoothstep(-1.0, 1.0, u_Phase) - 0.5) * 2.0;
    } else {
        int pairCount = u_InstanceCount / 2;
        float pairProgress = pairID / float(pairCount);
        float myTime = max(u_Phase - (pairProgress) * 0.5, 0.0) * 2.0;
        phase = (smoothstep(-1.0, 1.0, myTime) - 0.5) * 2.0;
    }



    // Generating object
    float radius = (log2(u_InstanceCount) + 3.0) * pow(r3.x, 1.0 / 3.0);
    float azimuth = r3.y * TWO_PI;
    float cosTheta = r3.z * 2.0 - 1.0;
    float theta = acos(cosTheta);
    vec3 rad = vec3(radius, azimuth, theta);
    vec3 offset = cartesian(rad);
    vec3 color = pickMainColor(gl_InstanceID, true);
    vec3 position = vertPosition.xyz;
    position *= u_Size;

    int parity = (gl_InstanceID % 2 == 0) ? 1 : -1;

    offset += normalize(r3 - 0.5) * (log2(u_InstanceCount) + 1.0) * parity * phase * u_Range;

    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;
    angle += (q3 - 0.5) * parity * phase;

    if (phase > 0.0) {
        color += vec3(10.0, 1.0, 0.0) * (1.0 - phase) * 10.;
    }

//    color = vec3(pairProgress);
    color = AMBER_ORANGE;

//    position *= 100.;
//    offset.x += 0.;
//    offset.z += 0.;
//    offset.y += -70.;

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ GAT ]============================
// ===========================================================

EffectResult effectGAT() {
    vec3 position = vertPosition.xyz;
    vec3 color;
    vec3 offset;

    vec3 r3 = irand3(gl_InstanceID + 1);
    vec3 q3 = irand3(-gl_InstanceID -1);

    // Generating object
    offset = generateOffset(gl_InstanceID + u_Seed, u_CloudRadius, false);

//    float progress = fract(time * 0.2);
    float progress = u_Progress;
//    float progress = 0.0;

    float progressDelayed = progress * 2.0 - r3.x;
    progressDelayed = clamp(progressDelayed, 0.0, 1.0);
    float progressShake = smoothstep(0.0, 0.5, progressDelayed);
//    float progressFly = smoothstep(0.5, 1.5, progressDelayed) * 2.0;
    float progressFly = smoothstep(0.5, 1.0, progressDelayed);



    vec3 farOffset = offset * u_GatherFactor;
//    farOffset.x = 0.0;
//    vec3 offsetShake = sin(q3 * time * 200.) * 0.04 * progressShake;
//    vec3 offsetShake = sin(((q3 * 0.5) + 0.5) * time * 400.) * 0.04 * progressShake;
    vec3 offsetShake;
    if (!u_FirstAccent || gl_InstanceID == 1) {
        offsetShake = sin(((q3 * 0.5) + 0.5) * time * 400.) * 0.04 * progressShake;
    } else {
        offsetShake = vec3(0.0);
    }
    int groupID = getGroup(gl_InstanceID + u_Seed, u_GroupCount, 0.22);  // 0.6

    vec3 offsetStart = farOffset + offsetShake;
    vec3 offsetSwirl = swirl(offset, 1.0);
    vec3 offsetStop = rot(offsetSwirl, vec3(0.0, 0.0, time * 2.));
//    offsetStop *= 0.5;
    if (u_UseGroupOffset) {
        vec3 s3 = (irand3(groupID + u_Seed) - 0.5) * 12.0;
        offsetStart += s3;
        offsetStop += s3;
    }
    offsetStart.z = u_FloorLevel;
    offsetStop.z = u_FloorLevel;

    if (!u_FirstAccent && groupID == 1) {
        offset = offsetStop;
    } else {
        offset = mix(offsetStart, offsetStop, progressFly);
    }

    if ((u_FirstAccent && groupID == 0) || (!u_FirstAccent && groupID == 1)) {
//        color = ICE_BLUE_CRYSTAL;
//        color = AMETHYST_PURPLE;
//        color = BIOLUMINESCENT_GREEN;
        color = AMBER_ORANGE;
    } else {
        color = pickMainColor(gl_InstanceID, true);
    }

    if (u_Colorize) {
        color += groupColor(groupID + 2 + u_Seed) - 0.5;
    }

    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ GRO ]============================
// ===========================================================

EffectResult effectGRO() {
    vec3 position = vertPosition.xyz;
    vec3 color;
    vec3 offset;

    vec3 r3 = irand3(gl_InstanceID + u_Seed);

    // Generating object
    offset = generateOffset(gl_InstanceID + u_Seed, u_CloudRadius, true);

//    float progress = fract(time * 0.2);
//    float progress = u_Progress;

    if (u_FirstAccent && gl_InstanceID < 2) {
//        color = BURN_COL;
        color = AMBER_ORANGE;
    } else {
        color = pickMainColor(gl_InstanceID, true);
    }

    int groupID = getGroup(gl_InstanceID + u_Seed, u_GroupCount, 1.3);

    if (u_Colorize) {
        color += groupColor(groupID + u_Seed) - 0.5;
    }

    vec3 groupOffset = generateOffset(groupID + u_Seed, u_GroupRadius, false);


    vec3 vector = move(offset, -groupOffset * 0.9, progress, u_CloudRadius);
    offset = swirl(offset, 1.0);
    offset = rot(offset, vec3(0.0, 0.0, time * 2.));
    offset += groupOffset;
    offset += vector;
    if (u_UseFloor) {
        offset.z = mix(u_FloorLevel, offset.z, smoothstep(0.0, 1.0, progress));
    }

    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ NE2 ]============================
// ===========================================================

EffectResult effectNE2() {
    vec3 position = vertPosition.xyz;
    vec3 color;
    vec3 offset;

    vec3 r3 = irand3(gl_InstanceID);

    // Generating object
    vec3 swarm = generateOffset(gl_InstanceID, u_CloudRadius, true);
    vec4 nest = generateNest2(-gl_InstanceID, u_CloudRadius, true);
    offset = nest.xyz;

    float morph = progress;
//    float morph = 1.0;

    float alpha;
    bool enableTongue;

    if (r3.x < 0.01) {
        color = AMBER_ORANGE;
        offset.xy *= 1.0 + (sin(r3.y * time * 5.) * 0.5 + 0.5);
        offset = swirl(offset, 1.0);
        offset = rotZ(offset, time * 2.0);
        enableTongue = false;
    } else {
        float kernel = (simplex3d(vec3(time * 1.0, 0.0, 0.0)) + 1.0) * 0.4;
        color = mix(pickMainColor(gl_InstanceID, true), AMBER_ORANGE, smoothstep(kernel, 0.0, length(offset) / u_CloudRadius));
        color *= nest.w * 2.0;// Nest out
        alpha = nest.w;
        offset.xy *= nest.w * 0.5 + 0.5;// Nest out
        offset = rotX(offset, -HALF_PI*2./3.);
        if (offset.z < 0.0) {
            float bottom = length(offset.xy) / u_CloudRadius;
            offset.z *= 2.0 - bottom;
        } else {
            float bottom = smoothstep(0.2, 0.1, length(offset.xy) / u_CloudRadius);
            offset.z *= 1.0 + bottom;
        }
        offset.xy *= 1.25;
        enableTongue = true;
    }

    position *= mix(1.0, 0.15, morph);
    swarm = swirl(swarm, 1.0);
    swarm = rotZ(swarm, time * -2.0);
    offset = mix(swarm, offset, morph);
    color = mix(pickMainColor(gl_InstanceID, true), color, morph);

    float threshold = mix(1000, u_InstanceCount, morph);
    if (float(gl_InstanceID) < threshold) {
        alpha = mix(1.0, alpha, morph);
    } else {
        alpha = 0.0;
    }

    // Tongue
    if (enableTongue) {
        if (u_Out) {
            float tongue = u_Tongue;
            float fly = smoothstep(0.0, u_CloudRadius, (offset.y + tongue * u_CloudRadius - u_CloudRadius) * 0.5) * 2.0;
            offset.y -= fly * 45.;
            offset.xz *= smoothstep(0.5, 0.0, fly) * 0.95 + 0.05;  // Shrink on fly
            offset.xz += sin(time * irand(gl_InstanceID)) * fly; // Wobble on fly
            offset.z -= smoothstep(0.0, 1.0, fly) * 20.;
        } else {
            float tongue = u_Tongue;
            float fly = smoothstep(0.0, u_CloudRadius, (offset.y + tongue * u_CloudRadius - u_CloudRadius) * 0.5) * 2.0;
            float randTongue = irand(gl_InstanceID + 123456789);
            if (fly > randTongue) {
                offset.y -= fly * 45.;// 45.
                offset.xz *= smoothstep(0.5, 0.0, fly) * 0.95 + 0.05;// Shrink on fly
                offset.xz += sin(time * irand(gl_InstanceID)) * fly;// Wobble on fly
                offset.z -= smoothstep(0.0, 1.0, fly) * 20.0;
                offset.z += smoothstep(0.4, 0.7, fly) * 15.0;// Lift on fly
                offset.x += smoothstep(0.5, 1.0, fly) * 30.0;// Lift on fly
            }
        }
    }


    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;
//    vec3 angle = vec3(0.0);

    return EffectResult(position, vec4(color, alpha), offset, angle);
}


// ===========================================================
// ========================[ DNA ]============================
// ===========================================================

EffectResult effectDNA() {
    #define RAD 30.0
    #define LEN 400.0
    #define THR 0.5

    vec3 cloud = generateOffset(gl_InstanceID + 12345, RAD, true);
    cloud = swirl(cloud, 1.0);

    vec3 part = irand3(gl_InstanceID);

    vec3 random = irand3(-gl_InstanceID);

//    InstanceData data = fetchInstanceData(u_DNAData, index);
//    vec3 offset = data.offset;
//    vec3 color = data.color;

    vec3 offset = vec3(0.0);
    vec3 color = pickMainColor(gl_InstanceID, true);

    if (part.x < THR) {
        vec3 move = rot(vec3(0.0, (random.x - 0.5) * 5.0, 0.0), vec3(time * random.x * 2.0, 0.0, 0.0));
        offset.z = sign(part.x - THR * 0.5) * RAD;
        offset.x = (part.y - 0.5) * LEN;
        offset.y = 0;
        offset += move;
//        color = vec3(1.0, 0.0, 0.0);
    } else {
        vec3 move = rot(vec3((random.y - 0.5) * 5.0, 0.0, 0.0), vec3(0.0, 0.0, time * random.y * 2.0));
        offset.z = ((part.x - THR) / (1.0 - THR) - 0.5) * 2. * RAD;
        offset.x = (part.y - 0.5) * LEN;
        offset.x = offset.x - mod(offset.x, 10.0);
        offset.y = 0;
        offset += move;
//        color = vec3(0.0, 0.0, 1.0);
    }



    vec3 position = vertPosition.xyz;


    offset = mix(cloud, offset, progress);

//    float alpha = smoothstep(LEN / 2., 0.0, abs(offset.x));
    float alpha;
    if (part.z > smoothstep(LEN / 2., 0.0, abs(offset.x))) {
        alpha = 0.0;
    } else {
        alpha = 1.0;
    }


    float move = sin(time) * 160.0;
    offset = rotX(offset, offset.x * -0.025 - smoothstep(-40. + move, 40. + move, offset.x) * PI);

    if (irand(-gl_InstanceID + 987654321) < 0.01) {
//        color = vec3(200.0, 15.0, 0.0);
        color = DNA_COL;
    }

    InstanceData prev;
    prev = fetchInstanceData2D(u_ROBData, index);
    color = mix(prev.color, color, progress);

    vec3 angle = (random - 0.5) * time * 5.0;


    vec3 beamOffset;
    vec3 beamColor;
    vec3 r3 = irand3(gl_InstanceID);
    InstanceData data = beams(gl_InstanceID);
    beamOffset = data.offset;
    beamColor = data.color;
    vec3 beamAngle = (r3 - 0.5) * time * 5.0;  // Rotating particles

    offset = mix(offset, beamOffset, u_BeamProgress);
    color = mix(color, beamColor, u_BeamProgress);
    angle = mix(angle, beamAngle, u_BeamProgress);
    alpha = mix(alpha, 1.0, u_BeamProgress);

//    offset = beamOffset;
//    color = beamColor;
//    angle = beamAngle;
//    alpha = 1.0;

//    alpha = 0.0;

    return EffectResult(position, vec4(color, alpha), offset, angle);
}


// ===========================================================
// ========================[ WAT ]============================
// ===========================================================

EffectResult effectWAT() {
    vec3 r3 = irand3(gl_InstanceID);

    #define SPREAD 20.0

    vec3 position = vertPosition.xyz * 0.2;
    vec3 color;
    color = pickMainColor(gl_InstanceID, true);
    vec3 offset;

    offset = vec3(0.0, (r3.y - 0.5) * SPREAD, (r3.z - 0.5) * SPREAD);

    offset.x += (getwaves5d(vec4(offset.yz * 0.1, 0.0, 0.0), 15.0, time * 2.0) -0.5) * 10.;

//    if (offset.x < 0.0) {
//        color = mix(MAIN_COL, ACCENT_COL, smoothstep(0.0, 5.0, -offset.x));
//    } else {
//        color = mix(MAIN_COL, BURN_COL, smoothstep(0.0, 5.0, offset.x));
//    }

//    offset = rot(offset, vec3(0.0, 0.0, time * 0.5));
    offset = rot(offset, vec3(0.0, 0.0, 1.0));

//    offset.y -= (sin(time * 1.5) * 0.5 + 0.5) * 8.0;

    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ COL ]============================
// ===========================================================

EffectResult effectCOL() {
    vec3 r3 = irand3(gl_InstanceID);
    vec3 q3 = irand3(-gl_InstanceID);
    int parity = (gl_InstanceID % 2 == 0) ? 1 : -1;

    #define SPREAD 20.0

    vec3 position = vertPosition.xyz * 0.5;
    vec3 color;
    color = pickMainColor(gl_InstanceID, true);
    vec3 offset;

    offset = vec3((r3.x - 0.5) * SPREAD, 0.0, (r3.z - 0.5) * SPREAD);
    offset = rot(offset, vec3(0.0, 0.0, r3.y * TWO_PI));

//    offset.x += (getwaves5d(vec4(offset.yz * 0.1, 0.0, 0.0), 15.0, time * 2.0) -0.5) * 10.;
//    if (offset.x < 0.0) {
//        color = mix(MAIN_COL, ACCENT_COL, smoothstep(0.0, 5.0, -offset.x));
//    } else {
//        color = mix(MAIN_COL, BURN_COL, smoothstep(0.0, 5.0, offset.x));
//    }
//
    offset = rot(offset, vec3(0.0, 0.0, time * 0.5));
    offset.x += parity * 10.;

    if (q3.x < 0.01) {
        color = MIDNIGHT_BLUE;
    } else {
        color = pickMainColor(gl_InstanceID, true);
    }

    color = mix(color, BURN_COL, smoothstep(0.5, 0.0, abs(offset.x)));
//    color = mix(color, BURN_COL, smoothstep(1.0, 0.0, abs(offset.x)));


//
    offset.y -= (sin(time * 0.66) * 0.5) * 24.0;

    // Rotating particles
    vec3 angle = (r3 - 0.5) * time * 5.0;

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}


// ===========================================================
// ========================[ TOR ]============================
// ===========================================================

EffectResult effectTOR() {

    vec4 r4 = irand4(gl_InstanceID);
    vec3 r2 = irand3(-gl_InstanceID);
//    vec3 q3 = irand2(-gl_InstanceID);

//    float progress = fract(time * 0.05);
//    float progress = u_Progress;

//    progress -= 0.2;

    // Generating floor object
    vec3 q3 = irand3(gl_InstanceID + 987654321);
    float distance = (1.0 - 1.0 / q3.x) * 320.;
    float direction = q3.y * TWO_PI;
    vec3 floor;
    floor.x = distance * cos(direction);
    floor.y = distance * sin(direction);
//    floor.z = -80.0 + simplex3d(vec3(floor.xy * 0.002, osg_FrameTime * 0.5)) * 80.;
//    floor.z = -80.0 + getwaves5d(vec4(floor.xy * 0.01, 0.0, 0.0), 0.0, osg_FrameTime) * (40. + length(floor.xy) * 0.2) - 20.;
    floor.z = -80.0 + getwaves5d(vec4(floor.xy * 0.005, 0.0, 0.0), 0.0, time) * 80. - 10.;

    // Generating object
    vec3 offset;
    offset.z = (r4.z) * 160.;
    float h;
    if (u_Pipe) {
        h = 160.;
    } else {
        h = offset.z;
    }
    float base_radius = 1.;
    float spread = 0.2;
    float radius = base_radius + pow(h * 0.1, 2.0) * spread * (sin(time * 2.0 - h * 0.2) * 0.1 + 1.0);

    radius *= 1.0 + pow(r2.z, 40.) * 2.76;

    offset.xy = circle(radius, (r4.y * 0.8 + 0.6) * (time * 5.0 + 43758.5453123) * 0.5);

    vec2 spark = (r2.xy - 0.5) * 5.;

    vec2 move;
    move.x = sin(0.9 * time - offset.z * 0.05) * 20.;
    move.y = cos(1.1 * time - offset.z * 0.05) * 20.;
    offset.xy += (move + spark) * offset.z * 0.01;



//    offset.z = (r4.z - 0.5) * sin(r2.x * time) * 30.;
    vec3 position = vertPosition.xyz * 2.;
    vec3 color;
    if (r4.w < 0.01) {
//        color = vec3(200.0, 15.0, 0.0);
        color = MIDNIGHT_BLUE;
    } else {
        color = pickMainColor(gl_InstanceID, false);
    }

    float hi = offset.z / 160.;
    float ra = length(offset.xy) / 320. * (1.0 - progress);

    offset.z -= 80.;

//    progress = 0.0;

    offset.xy *= (q3.z - 0.5) * 0.7 + 1.0;

    float p = smoothstep(0.9, 2.0, progress * 2.0 + hi + ra);

    offset = mix(floor, offset, p);

    vec3 cloud = generateOffset(gl_InstanceID, u_CloudRadius, true);
    cloud = swirl(cloud, CCC_RAD_SCALE, -1.0, CCC_OFF_SCALE);

    float morph = u_Morph;
//    morph = 1.0;
    offset = mix(offset, cloud, morph);
    color = mix(color, pickMainColor(gl_InstanceID, false), morph);

    if (u_Pipe) {
        offset.z *= 4.0;
    }

    color *= pow(abs(simplex3d(offset * 0.0075 - time * 0.3)), 4.0) * 500. * u_Thunder + 1.0;

    // Rotating particles
    vec3 angle = (r4.xyz - 0.5) * time * 5.0;

    return EffectResult(position, vec4(color, 1.0), offset, angle);
}

// ===========================================================
// ========================[ NON ]============================
// ===========================================================

//EffectResult effectNON(vec3 position, vec3 color, vec3 offset) {
EffectResult effectNON() {
    int frame = 9;
//    InstanceData data = fetchInstanceData(u_InstanceData, frame * u_InstanceCount * 2 + index);
//    vec3 offset = data.offset;
//    vec3 color = data.color;
    vec3 offset = vec3(0.0);
    vec3 color = vec3(0.0);
    vec3 position = vertPosition.xyz;
    return EffectResult(position, vec4(color, 0.0), offset, vec3(0.0));
}


EffectResult getEffect(int effect) {
    switch (effect) {
        case SWA: return effectSWA();
        case OBJ: return effectOBJ();
        case RIN: return effectRIN();
        case TOR: return effectTOR();
        case DIV: return effectDIV();
        case GAT: return effectGAT();
        case WAT: return effectWAT();
        case COL: return effectCOL();
        case GRO: return effectGRO();
        case BEA: return effectBEA();
        case FIE: return effectFIE();
        case CHI: return effectCHI();
        case NE2: return effectNE2();
        case DNA: return effectDNA();
        case CCC: return effectCCC();
        default:  return effectNON();
    }
}

// Dokładny odpowiednik Rz * Ry * Rx z Twojego rot()
mat3 eulerZYX(vec3 a) {
    float cx = cos(a.x), sx = sin(a.x);
    float cy = cos(a.y), sy = sin(a.y);
    float cz = cos(a.z), sz = sin(a.z);
    return mat3(
        cy*cz,               -cx*sz + cz*sx*sy,   cx*cz*sy + sx*sz,
        cy*sz,                cx*cz + sx*sy*sz,   cx*sy*sz - cz*sx,
        -sy,                  cy*sx,               cx*cy
    );
}

void main() {
    vertPosition = (p3d_Vertex + vec4(u_VertPositionOffset, 0.0)) * u_Scale;
//    vertColor = p3d_Color;

    cloudSpecular = 1.0;

    EffectResult effect = getEffect(u_Effect);

    vertPosition.xyz = effect.position;
    vertColor = effect.color;
    vec3 angle = effect.angle;
    vertPosition.xyz = rot(vertPosition.xyz, angle);
    vec3 vertOffset = effect.offset;
    vertPosition.xyz += vertOffset;

    vertPosition = p3d_ModelViewMatrix * vertPosition;
    gl_Position = p3d_ProjectionMatrix * vertPosition;
    // Transform vertex position into shadow spaces for each light
    for (int i = 0; i < u_NumberOfLights; ++i) {
        vertInShadowSpaces[i] = p3d_LightSource[i].shadowViewMatrix * vertPosition;
    }

    float cx = cos(angle.x), sx = sin(angle.x);
    float cy = cos(angle.y), sy = sin(angle.y);
    float cz = cos(angle.z), sz = sin(angle.z);
    mat3 rotX = mat3(
        1.0, 0.0, 0.0,
        0.0,  cx, -sx,
        0.0,  sx,  cx
    );
    mat3 rotY = mat3(
         cy, 0.0, sy,
         0.0, 1.0, 0.0,
        -sy, 0.0, cy
    );
    mat3 rotZ = mat3(
         cz, -sz, 0.0,
         sz,  cz, 0.0,
         0.0, 0.0, 1.0
    );
    mat3 rotationMatrix = rotZ * rotY * rotX;
    vec3 transformedNormal = rotationMatrix * p3d_Normal;
    vertNormal = normalize(p3d_NormalMatrix * transformedNormal);

}


