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 = (targetTexture, lumaTexture) => new THREE.RawShaderMaterial({
    transparent: true,
    uniforms: {
      targetMap: {
        value: targetTexture
      },
      lumaMap: {
        value: lumaTexture
      }
    },
    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 targetMap;
    uniform sampler2D lumaMap;

    varying vec2 vUv;

    void main() {

      vec4 targetTexel = texture2D( targetMap, vUv );
      vec4 lumaTexel = texture2D( lumaMap, vUv );

      float maskLuminance = 0.2126 * lumaTexel.r + 0.7152 * lumaTexel.g + 0.0722 * lumaTexel.b;
      
      gl_FragColor = vec4(targetTexel.rgb, maskLuminance * targetTexel.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 targetTextureSrc = 'images/wall.jpg';
  const lumaTextureSrc = 'video/scene_3_key.mp4';
  
  // Load video
  const video = document.createElement( 'video' );
  video.src = lumaTextureSrc;
  video.loop = true;
  video.load(); // must call after setting/changing source
  video.play();
  
  const hasWebGLSupport = (() => THREE.WEBGL.isWebGLAvailable() || THREE.WEBGL.isWebGL2Available())();
  
  const infrastructure = setup3dInfrastructure();
  
  infrastructure.camera.position.z = 1100;
  
  const targetTexture = new THREE.TextureLoader().load(targetTextureSrc);
  const lumaTexture = new THREE.VideoTexture(video);
  const material = getEffectMaterial(targetTexture, lumaTexture);
  const plane = getPlaneLayer(material, 1024, 512);
  
  infrastructure.scene.add(plane);
  
  // Render:
  
  const onAnimationFrame = (infrastructure) => {
    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'));
}


start()


