const setup3dInfrastructure = () => {
        const initialWidth = window.innerWidth;
        const initialHeight = window.innerHeight;
  
        //
        // 1.1.1. Renderer
        const renderer = new THREE.WebGLRenderer({
            alpha: true,
            antialias: true,
            logarithmicDepthBuffer: true
        });

        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(initialWidth, initialHeight);
        renderer.setClearColor(0xff0000, 0);

        //
        // 1.1.2. Scene
        const scene = new THREE.Scene();

        //
        // 1.1.3. Camera
        /*
         fov — Camera frustum vertical field of view.
         aspect — Camera frustum aspect ratio.
         near — Camera frustum near plane.
         far — Camera frustum far plane.
        * */

        const params = {
            fov: 35,
            aspect: initialWidth / initialHeight,
            near: 1,
            far: 5500
        };
        const camera = new THREE.PerspectiveCamera(params.fov, params.aspect, params.near, params.far);
        
        function resize() {
          const width = window.innerWidth;
          const height = window.innerHeight;
  
          if (height < 1 || width < 1) return;
  
          // 3.1. Camera resize
          if (width > height) {
              this.camera.fov = 35;
          } else {
              this.camera.fov = (32 * height) / Math.min(width * 1.3, height);
          }
          this.camera.aspect = width / height;
          this.camera.updateProjectionMatrix();
  
          // 3.3. Renderer resize
          this.renderer.setSize(width, height);
  
          return this;
      }
      
      const infrastructure = { 
          renderer, 
          scene, 
          camera
          };
          
      infrastructure.resize = resize.bind(infrastructure);

        return infrastructure;
    };
    


const getCustomMaterial = (texture) => {
      
        const material = new THREE.MeshBasicMaterial({ color: 0xFFFFFF, map: texture, side: THREE.FrontSide });

        material.needsUpdate = true;

        return material;
    };
    
const getEffectMaterial = (texture, amplitude = 1) => new THREE.RawShaderMaterial({
    uniforms: {
        map: {
            value: texture
        },
        noiseAmplitude: {
            value: amplitude
        },
        noiseSize: {
            value: 2
        }
    },
    vertexShader: `
        uniform mat4 projectionMatrix;
        uniform mat4 modelMatrix;
        uniform mat4 viewMatrix;

        attribute vec3 position;
        attribute vec3 normal;
        attribute vec2 uv;

        varying vec2 vUv;

        void main() {

            vUv = uv;

            gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 );

        }`,
    
    fragmentShader: `
        precision mediump float;

        uniform sampler2D map;
        uniform float noiseAmplitude;
        uniform float noiseSize;

        varying vec2 vUv;

        highp float rand(const in vec2 uv) {
            return fract(sin(dot(uv.xy, vec2(12.9898,78.233))) * 43758.5453123);
        }

        void main() {

            vec4 texel = texture2D( map, vUv );

            float xNormalized = floor(gl_FragCoord.x / noiseSize);
            float yNormalized = floor(gl_FragCoord.y / noiseSize);

            float amp = noiseAmplitude;

            
            float mixFrequency = 0.002;

            vec4 noiseColor = 0.08 * amp * vec4(rand(vec2(xNormalized * mixFrequency, yNormalized * mixFrequency)));

            gl_FragColor = texel + vec4(noiseColor.rgb, texel.a);

        }`
});

const getPlaneLayer = (material, width, height) => {
      
        const geometry = new THREE.PlaneBufferGeometry(width, height);

        const plane = new THREE.Mesh(geometry, material);

        material.needsUpdate = true;

        return plane;
    };


const appendRendererToDOMElement = (renderer, targetNode) => {
        targetNode.appendChild(renderer.domElement);
    };

function start() {
  const textureSrc = 'images/1024x512/scene_1_hat.png';
  
  const hasWebGLSupport = (() => THREE.WEBGL.isWebGLAvailable() || THREE.WEBGL.isWebGL2Available())();
  
  const infrastructure = setup3dInfrastructure();
  
  infrastructure.camera.position.z = 1100;
  
  const texture = new THREE.TextureLoader().load(textureSrc);
  const material = getEffectMaterial(texture);
  const plane = getPlaneLayer(material, 1024, 512);
  
  const noiseTweenSettings = {
    amp: 1,
    prevAmp: 1,
    newAmp: 1,
    duration: 0.5,
    timeStart: -1
  };
  
  infrastructure.scene.add(plane);
  
  // Render:
  
  const onAnimationFrame = (infrastructure) => {
    
        let ampValue = noiseTweenSettings.amp;
    
        // Process tween
        if (noiseTweenSettings.progress < 1 && noiseTweenSettings.timeStart > 0) {
          
          const t = Date.now() * 0.001 - noiseTweenSettings.timeStart;
          
          if (t >= noiseTweenSettings.duration) {
            noiseTweenSettings.timeStart = -1;
            ampValue = noiseTweenSettings.newAmp;
            noiseTweenSettings.prevAmp = ampValue;
          }
          
          const progress = t / noiseTweenSettings.duration;
          
          // ease: SineInOut
          const easeProgress = (1 - Math.cos(progress * Math.PI)) * 0.5;
          
          ampValue = noiseTweenSettings.prevAmp + (noiseTweenSettings.newAmp - noiseTweenSettings.prevAmp) * easeProgress;
        }
        //
        
        // Update amplitude
        if (ampValue !== noiseTweenSettings.amp) { 
          material.uniforms.noiseAmplitude.value = ampValue;
          material.needsUpdate = true;
          
          // Store the new amplitude value
          noiseTweenSettings.amp = ampValue;
        }
        
        if (infrastructure.resizeInProgress) infrastructure.resize();

        infrastructure.renderer.render(infrastructure.scene, infrastructure.camera);

        infrastructure.resizeInProgress = false;

        requestAnimationFrame(() => {
            onAnimationFrame(infrastructure);
        });
    };
  
  requestAnimationFrame(() => {
            onAnimationFrame(infrastructure);
    });
  
  //
  // Resize:
  window.addEventListener('resize', () => {
    if (infrastructure.resizeInProgress) return;
        infrastructure.resizeInProgress = true;
  });
  
  //
  // Add to DOM:
  appendRendererToDOMElement(infrastructure.renderer, document.getElementById('canvas-3d-container'));
  
  // Add toggle handler
  toggleNoiseAmplitude = () => {
    noiseTweenSettings.newAmp = noiseTweenSettings.newAmp > 0 ? 0 : 1;
    noiseTweenSettings.prevAmp = noiseTweenSettings.amp;
    noiseTweenSettings.progress = 0;
    noiseTweenSettings.timeStart = Date.now() * 0.001;
  };
  
  
  const toggleButton = document.querySelector('.test-control .toggle-button');
  toggleButton.addEventListener('click', () => {
    toggleNoiseAmplitude();
  });
  
}


start()


