control/SimpleSkyBackground.tstypescript
import { Control } from "./Control";
import type { IControlParams } from "./Control";
import { htmlColorToRgb, rgbToStringHTML } from "../utils/shared";
import { srgbToLinear } from "../utils/colorSpace";
import { ShaderProgram } from "../webgl/ShaderProgram";
import type { NumberArray3 } from "../math/Vec3";

interface ISimpleSkyBackgroundParams extends IControlParams {}

export class SimpleSkyBackground extends Control {
    protected _colorOne: Float32Array;
    protected _colorTwo: Float32Array;

    constructor(options: ISimpleSkyBackgroundParams = {}) {
        super({
            name: "SimpleSkyBackground",
            ...options
        });

        this._colorOne = new Float32Array([srgbToLinear(128 / 255), srgbToLinear(223 / 255), srgbToLinear(255 / 255)]);
        this._colorTwo = new Float32Array([srgbToLinear(10 / 255), srgbToLinear(15 / 255), srgbToLinear(56 / 255)]);
    }

    public get colorOne(): string {
        let c = this._colorOne;
        let arr: NumberArray3 = [Math.round(c[0] * 255), Math.round(c[1] * 255), Math.round(c[2] * 255)];
        return rgbToStringHTML(arr);
    }

    public get colorTwo(): string {
        let c = this._colorTwo;
        let arr: NumberArray3 = [Math.round(c[0] * 255), Math.round(c[1] * 255), Math.round(c[2] * 255)];
        return rgbToStringHTML(arr);
    }

    public set colorOne(htmlColor: string) {
        let rgb = htmlColorToRgb(htmlColor);
        this._colorOne[0] = srgbToLinear(rgb.x);
        this._colorOne[1] = srgbToLinear(rgb.y);
        this._colorOne[2] = srgbToLinear(rgb.z);
        this.renderer && this.renderer.requestRedraw();
    }

    public set colorTwo(htmlColor: string) {
        let rgb = htmlColorToRgb(htmlColor);
        this._colorTwo[0] = srgbToLinear(rgb.x);
        this._colorTwo[1] = srgbToLinear(rgb.y);
        this._colorTwo[2] = srgbToLinear(rgb.z);
        this.renderer && this.renderer.requestRedraw();
    }

    public override oninit() {
        this.renderer!.handler.addProgram(simpleSkyBackgroundShader());
        this.activate();
    }

    public override onactivate() {
        super.onactivate();
        this.planet!.events.on("draw", this._drawBackground, this);
    }

    public override ondeactivate() {
        super.ondeactivate();
        this.planet!.events.off("draw", this._drawBackground);
    }

    protected _drawBackground() {
        let h = this.renderer!.handler;
        let sh = h.programs.simpleSkyBackground,
            p = sh,
            shu = p.uniforms,
            gl = h.gl!;
        let cam = this.planet!.camera;

        gl.disable(gl.DEPTH_TEST);

        sh.activate();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.renderer!.screenFramePositionBuffer!);
        gl.vertexAttribPointer(p.attributes.corners, 2, gl.FLOAT, false, 0, 0);

        gl.uniform3fv(shu.camPos, [cam.eye.x, cam.eye.y, cam.eye.z]);
        gl.uniform2fv(shu.iResolution, [h.getWidth(), h.getHeight()]);
        gl.uniform1f(shu.fov, cam.getViewAngle());
        gl.uniform1f(shu.earthRadius, this.planet!.ellipsoid.getPolarSize() + 1);
        gl.uniform3fv(shu.colorOne, this._colorOne);
        gl.uniform3fv(shu.colorTwo, this._colorTwo);
        gl.uniformMatrix3fv(shu.normalMatrix, false, cam.getNormalMatrix());

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

        gl.enable(gl.DEPTH_TEST);
    }
}

function simpleSkyBackgroundShader(): ShaderProgram {
    return new ShaderProgram("simpleSkyBackground", {
        uniforms: {
            iResolution: "vec2",
            fov: "float",
            camPos: "vec3",
            earthRadius: "float",
            normalMatrix: "mat3",
            colorOne: "vec3",
            colorTwo: "vec3"
        },
        attributes: {
            corners: "vec3"
        },
        vertexShader: `attribute vec2 corners;
                        
            varying vec2 tc;
            
            void main(void) {
                gl_Position = vec4(corners, 0.0, 1.0);
                tc = corners * 0.5 + 0.5;
            }`,
        fragmentShader: `precision highp float;
            
            #define MAX 10e10
            #define PI 3.14159265359
            #define rad(x) x * PI / 180.
            #define ZERO vec3(0.0)          
           
            #define RED vec4(1.0, 0.0, 0.0, 1.0)
            #define GREEN vec4(0.0, 1.0, 0.0, 1.0)         
            
            uniform vec3 camPos;            
            uniform vec2 iResolution;
            uniform float fov;
            uniform float earthRadius;
            uniform mat3 normalMatrix;
            
            uniform vec3 colorOne;
            uniform vec3 colorTwo;
                         
            varying vec2 tc;
                        
            // compute the view ray in the camera coordinate
            vec3 computeView(vec2 uv){
                float w_h_ratio = iResolution.x / iResolution.y;   
                float h = tan(rad(fov/2.));
                return normalize(vec3(-w_h_ratio * h, -h, -1.) + vec3(uv.x * 2. * h * w_h_ratio, uv.y*2.*h, 0.));
            }

            // sphere of size ra centered at point ce
            vec2 sphIntersect( in vec3 ro, in vec3 rd, in vec3 ce, float ra )
            {
                vec3 oc = ro - ce;
                float b = dot( oc, rd );
                float c = dot( oc, oc ) - ra * ra;
                float h = b * b - c;
                if( h < 0.0 ) return vec2(MAX); // no intersection
                h = sqrt( h );
                return vec2( -b-h, -b+h );
            }
            
            void main(void) {
            
                vec3 dir = computeView(tc);
                dir = normalMatrix * dir;
                
                vec2 ER = sphIntersect(camPos, dir, vec3(0.0), earthRadius);
                
                float bigRadius = earthRadius * 2.5;
                vec3 bigCenter = normalize(camPos) * bigRadius * 1.3;                
                               
                vec2 BIG = sphIntersect(camPos, dir, bigCenter, bigRadius);
                
                float Ix = distance(camPos + dir * BIG.y, ZERO);               
                
                float maxI = sqrt(bigRadius * bigRadius + bigRadius * bigRadius);
                                   
                gl_FragColor = vec4(mix(colorOne, colorTwo, Ix / maxI), 1.0);
            }`
    });
}