[SOLVED] Help with spiral timer shader

I’ve made a shader that mimics the sprial cooldown/timer effect that many games use to indicate that an ability or action cannot be used until the timer ends.

I thought I got it working, but there appears to be some inconsistency in the speed of the spiral

In this gif you will notice that the spiral drastically speeds up for just a moment when it is just past the half way mark (from 6 to 7 o clock approximately) and slows down towards the end as well.

Here’s the code, I figured out a hacky way to do this using the dot product function as opposed to some other math related to triangles/circles that was beyond my math skills.

Unless my code is wrong and I’m overlooking something, then I’m starting to believe that using dot product here is not correct and doesn’t produce visually linear results even though it technically works. In which case I could use help doing this in a more mathematical way :slightly_smiling_face:

    vec3 centerPoint = vec3(0.0, 0.0, 0.0);
    
    vec3 dirToPixelPoint = normalize(vertCoords.xyz - centerPoint.xyz);        
    vec3 upVec = normalize(vec3(0, 0, 1));        
    
    float dotValue = dot(dirToPixelPoint, upVec);
    
    
    dotValue ++; //put all dot values in range of 0-2
    
    if(vertCoords.x < 0){ //put values on left side in range of 2-4
        
        dotValue = 2 - dotValue;
        dotValue += 2;
        
    } 
    
    
    dotValue *= 0.25; //put values back into range of 0-1
  
    if(dotValue > m_CurrentCooldownPercent){
        color.rgba = vec4(0.0004, 0.0004, 0.0005, 0.98);
    }else{
        color.rgba = vec4(0.0);
    }

I tried using vert coords as well as tex coords, both produce the same result. Also here’s the java code sending in the CurrentCooldownPercent value just to assure that I’m sending a proper value to the shader.

public void update(float tpf){
    float cdPctVal = 0f;
    if(bookItem != null){
        float currCd = bookItem.getCd();
        float totalCd = bookItem.getTotalCoolDown();
        
        if(currCd > 0){
            cdPctVal =  currCd / totalCd;
        }
    }
    
    if(actionOverlayMat != null){
       actionOverlayMat.setFloat("CurrentCooldownPercent", 1 - cdPctVal);
    }
}

Any help is appreciated :slightly_smiling_face: I also hope this code can maybe be useful to some other monkeys, especially once the speed inaccuracy is fixed.

1 Like

I’m pretty sure the dot product is not appropriate here (and that makes me sad because it’s my very favorite math thing). Admittedly, I do now know how you got where you are so maybe you derived this somehow… I’ve been heads down in “work breakdown structures” for the past two days and can’t properly math at the moment.

However, some red flags:

This vector will already be normalized by definition.

This will always be just dirToPixelPoint.z

So to be honest, unless there is code missing, I have trouble seeing how this works at all. For this code to produce what’s in your video, I’m definitely missing something.

I believe you are better off doing the trig functions here.

Something like:

vec3 dirToPixelPoint = normalize(vertCoords.xyz - centerPoint.xyz);  
float rads = atan2(dirToPixelPoint.y, dirToPixelPoint.x); // though your code indicates maybe switch .z for .y
float coolDownRads = m_CurrentCoolDownPercent * TWO_PI;  // make a TWO_PI constant
if( rads > coolDownRads ) {
    color.rgba = vec4(0.0004, 0.0004, 0.0005, 0.98);
} else {
    color.rgba = vec4(0.0);
}

There doesn’t seem to be a strong reason to worry about the cost of atan2 here but if you were really worried about it then you could precalculate a texture where each texel had the rads / TWO_PI gradient in it. Then just sample the texture and compare it to cool down percent.

Edit: I did a quick google for radial gradient texture for an illustration of what I mean above:

The upper left corner of this image:

2 Likes

Ok, I did one better. I was 99% sure that photoshop had this. So I made on for you.

This seems more linear than the one in the image I quoted above and it’s already clipped for you.

I still recommend trying the atan2() approach just for understanding. And no doubt, that’s what photoshop is using for this texture.

1 Like

Thank you the aTan2 function is exacly what I needed. It took me a bit to implement it and understand radians better, but everything is working now and the sprial is finally smooth. It probably would’ve been quicker to use the image based spiral, but I figured I’d use this as a learning opportunity as well.

I had to add some more code to convert negative radians to positive and put them in proper range to make the sprial cover the full 360 degree, rather than just 180. I think this is because the vec3 dirToPixelPoint I’m calculating from the models vert coords can be negative and returns a range of negative radians. So instead of getting a range of radians representing 0 through 360 degrees for the float value rads, I think I was getting a range of radians representing -180 through 180 and that was causing only half the spiral to work.

So I added this code after calculating rads value using the atan2 function, and everything is working now.

       if(rads < 0){
            rads += PI;
            rads *= 0.5;
            
            rads = PI - rads;
            
        }else{
            rads = PI - rads;
            rads *= 0.5;
        }

Thank you for the help, you never fail to help me solve and learn things that I previously thought were beyond my capabilities :slightly_smiling_face:

Here’s the full snippet of useful code in case anyone else ever needs the same functionality. And of course let me know if anything else looks like it can still be improved.

#define PI 3.14159
float ATan2(vec2 dir)
{
    float angle = asin(dir.x) > 0 ? acos(dir.y) : -acos(dir.y);
    return angle;
}

      ...
                
        vec3 dirToPixelPoint = normalize(vertCoords.xyz);  
        float rads = ATan2(vec2(dirToPixelPoint.x, dirToPixelPoint.z)); // though your code indicates maybe switch .z for .y
        
        if(rads < 0){
            rads += PI;
            rads *= 0.5;
            
            rads = PI - rads;
            
        }else{
            rads = PI - rads;
            rads *= 0.5;
        }    
        
        
        
        float coolDownRads = m_CurrentCooldownPercent * PI;  
        
        
        if( rads > coolDownRads ) {
            color.rgba = vec4(0.0004, 0.0004, 0.0005, 0.98);
        } else {
            color.rgba = vec4(0.0);
        }
1 Like

It’s true that atan2 will return -PI to PI. Once you divide by TWO_PI you can just add 0.5. Whether you want 0 degrees to be “up” or not is often a matter of what values you pass to atan2. But your math should be fine also.

I guess I didn’t realize that glsl does not have an atan2()… but it’s good you figured it out.

In the end, the texture based approach will be superior but not as much of a learning experience.

1 Like