<!DOCTYPE html>
<html lang="hu">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Benedek László - IH1RZJ - masodik beadandó: Lego Batman</title>
  <script async src="./dist/es-module-shims.js"></script>
  <script type="importmap">
    {
      "imports": {
        "three": "./js-r167/build/three.module.min.js",
        "TrackballControls": "./js-r167/examples/jsm/controls/TrackballControls.js",
        "OBJLoader": "./js-r167/examples/jsm/loaders/OBJLoader.js"
      }
    }
  </script>
  <style>
    body,
    html canvas {
      margin: 0;
      border: 0;
      padding: 0;
    }

    canvas {
      width: 100vw;
      height: 100vh;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>

  <script type="module">
    import * as THREE from "three";
    import { TrackballControls } from "TrackballControls";
    import { OBJLoader } from "OBJLoader";

    class SceneLoader {
      static FromJson(data) {
        let _scene = new THREE.Scene();

        let geometries = {};
        let materials = {};
        let objects = {};

        const geometryTypes = {
          "CircleGeometry": THREE.CircleGeometry,
          "PlaneGeometry": THREE.PlaneGeometry,
          "BoxGeometry": THREE.BoxGeometry,
          "SphereGeometry": THREE.SphereGeometry,
          "ConeGeometry": THREE.ConeGeometry,
          "CylinderGeometry": THREE.CylinderGeometry,
          "TorusGeometry": THREE.TorusGeometry,
        };

        const materialTypes = {
          "MeshBasicMaterial": THREE.MeshBasicMaterial,
          "MeshLambertMaterial": THREE.MeshLambertMaterial,
          "MeshPhongMaterial": THREE.MeshPhongMaterial,
          "MeshStandardMaterial": THREE.MeshStandardMaterial
        };

        for (let [key, value] of Object.entries(data.geometries)) {
          geometries[key] = new geometryTypes[value.type](...value.options);
        }

        for (let [key, value] of Object.entries(data.materials)) {
          materials[key] = new materialTypes[value.type](value.options);
          if (value.texture) {
            materials[key].map = new THREE.TextureLoader().load(value.texture.path);
            if (value.texture.repeat == true) {
              materials[key].map.wrapS = THREE.RepeatWrapping;
              materials[key].map.wrapT = THREE.RepeatWrapping;
            }
          }
        }

        function createObject(data, name) {
          const objectConstructors = {
            "empty": data => new THREE.Object3D(),
            "OBJ": data => {
              let empty = new THREE.Object3D();
              let loader = new OBJLoader();
              loader.load(
                data.url,
                result => {
                  result.traverse(element => {
                    if (element instanceof THREE.Mesh) {
                      element.material = materials[data.material];
                      if (data.castShadow) element.castShadow = data.castShadow;
                      if (data.receiveShadow) element.receiveShadow = data.receiveShadow;
                      empty.add(element);
                    }
                  })
                });
              return empty;
            },
            "Mesh": data => new THREE.Mesh(geometries[data.geometry], materials[data.material]),
            "LineSegments": data => new THREE.LineSegments(geometries[data.geometry], materials[data.material]),

            "AmbientLight": data => new THREE.AmbientLight(data.color ? data.color : 0xffffff, data.intensity ? data.intensity : 1),
            "PointLight": data => {
              let object = new THREE.PointLight(data.color ? data.color : 0xffffff, data.intensity ? data.intensity : 1);
              object.distance = data.distance ? data.distance : 100;
              return object;
            },
            "SpotLight": data => {
              let object = new THREE.SpotLight(data.color ? data.color : 0xffffff, data.intensity ? data.intensity : 1);
              object.angle = THREE.MathUtils.degToRad(data.angle ? data.angle : 45);
              if (data.position) object.position.set(...data.position);
              object.target = objects[data.target];
              object.distance = data.distance ? data.distance : 100;
              return object;
            },
            "DirectionalLight": data => {
              let object = new THREE.DirectionalLight(data.color ? data.color : 0xffffff, data.intensity ? data.intensity : 1);
              if (data.position) object.position.set(...data.position);
              object.target = objects[data.target];
              object.distance = data.distance ? data.distance : 100;
              object.shadow.camera.left = -50;
              object.shadow.camera.right = 50;
              object.shadow.camera.bottom = -50;
              object.shadow.camera.top = 50;
              return object;
            },
            "HemisphereLight": data => new THREE.HemisphereLight(data.color ? data.color : 0xffffff, data.groundColor ? data.groundColor : 0x00ff00, data.intensity ? data.intensity : 1),

            "PointLightHelper": data => new THREE.PointLightHelper(objects[data.target], data.sphereSize ? data.sphereSize : 5),
            "SpotLightHelper": data => new THREE.SpotLightHelper(objects[data.target]),
            "DirectionalLightHelper": data => new THREE.DirectionalLightHelper(objects[data.target], data.planeSize ? data.planeSize : 10),
            "HemisphereLightHelper": data => new THREE.HemisphereLightHelper(objects[data.target], data.sphereSize ? data.sphereSize : 5),
            "AxesHelper": data => new THREE.AxesHelper(data.helperSize ? data.helperSize : 10),
            "ArrowHelper": data => new THREE.ArrowHelper(...(data.direction ? data.direction : [1, 0, 0]), ...(data.origin ? data.origin : [0, 0, 0]), data.length ? data.length : 10, data.color ? data.color : 0xff00ff),
            "ShadowCameraHelper": data => new THREE.CameraHelper(objects[data.target].shadow.camera),
          };

          // create object
          let type = data.type ? data.type : "Mesh";
          let object = (objects[name] = objectConstructors[type](data));

          if (data.position) object.position.set(...data.position);
          if (data.rotation) object.rotation.set(...Array.from(data.rotation, x => THREE.MathUtils.degToRad(x)));
          if (data.scale) object.scale.set(...data.scale);
          if (data.castShadow) object.castShadow = data.castShadow;
          if (data.receiveShadow) object.receiveShadow = data.receiveShadow;
          if (data.animation) object.animation = data.animation;

          // add children to parent
          if (data.children) {
            for (let [key, value] of Object.entries(data.children)) {
              object.add(createObject(value, key));
            }
          }

          return object;
        }

        for (let [key, value] of Object.entries(data.objects)) {
          _scene.add(createObject(value, key));
        }

        return _scene;
      }
    };

    const fov = 75;
    const near = 0.1;
    const far = 10000;
    const rotateSpeed = 5.0;
    const panSpeed = 1.0;

    const loop = true;

    let canvas, renderer, scene, camera, controls, clock;

    init().then(loop && animate());

    async function init() {
      canvas = document.querySelector("#canvas");
      renderer = new THREE.WebGLRenderer({ antialias: true, canvas: canvas, alpha: true });
      renderer.shadowMap.enabled = true;
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setClearColor("black");

      camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, near, far);
      camera.position.set(0, 10, 50);

      controls = new TrackballControls(camera, canvas);
      controls.rotateSpeed = rotateSpeed;
      controls.panSpeed = panSpeed;

      clock = new THREE.Clock();

      scene = await fetch("scene.json")
        .then((response) => response.json())
        .catch(reason => console.log(`fetch failed: ${reason}`))
        .then((json) => SceneLoader.FromJson(json));

      window.addEventListener("resize", handleWindowResize, false);

      render();
    }

    function handleWindowResize() {
      console.log(`resize: ${window.innerWidth}x${window.innerHeight}`)
      renderer.setSize(window.innerWidth, window.innerHeight);

      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();

      render();
    }

    function animate() {
      requestAnimationFrame(animate);
      controls.update();

      let delta = clock.getDelta();
      let time = clock.getElapsedTime();

      scene.traverse((element)=>{
        if (element.animation) {
          (() => {
            eval(element.animation);
          })
          .call({ element: element, delta: delta, time: time });
        }
      })

      render();
    }

    function render() {
      renderer.render(scene, camera);
    }
  </script>
</body>

</html>