TA159

Notas, resueltos y trabajos practicos de la materia Sistemas Gráficos
Index Commits Files Refs Submodules README LICENSE
commit 2d3e3a6b7249d8eef971e524d68611297114a326
parent 6e2641f555d0d672025a5f87418436e3f4d29999
Author: Martin J. Klöckner <mjkloeckner@gmail.com>
Date:   Sat, 13 Jul 2024 18:23:44 -0300

Merge pull request #3 from mjkloeckner/shadows

Add shadows to all objects in scene
Diffstat:
Mtp/src/bridge.js | 27+++++++++++++++++----------
Mtp/src/scene.js | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mtp/src/standalone/rails.js | 3+++
Mtp/src/terrain.js | 1-
Mtp/src/train.js | 38++++++++++++++++++++++++++------------
Mtp/src/trees.js | 2+-
6 files changed, 256 insertions(+), 124 deletions(-)
diff --git a/tp/src/bridge.js b/tp/src/bridge.js
@@ -228,13 +228,10 @@ export function generateBridge(arcCount=1, arcRadius=3,
     textures.ladrillos.object.wrapS = THREE.RepeatWrapping;
     textures.ladrillos.object.wrapT = THREE.RepeatWrapping;
     textures.ladrillos.object.repeat.set(0.75*0.15, 0.75*0.35);
-    textures.ladrillos.object.anisotropy = 16;
+    // textures.ladrillos.object.anisotropy = 16;
 
     const bridgeMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        transparent: false,
-        opacity: 1.0,
-        shininess: 10,
+        side: THREE.FrontSide,
         map: textures.ladrillos.object
     });
 
@@ -258,6 +255,9 @@ export function generateBridge(arcCount=1, arcRadius=3,
     */
 
     const bridgeColumns = new THREE.Mesh(bridgeColumnsGeometry, bridgeMaterial);
+    bridgeColumns.castShadow    = true;
+    bridgeColumns.receiveShadow = true;
+
     bridge.add(bridgeColumns);
 
     // para reutilizar la textura de ladrillos usada en los arcos se escalan las
@@ -270,11 +270,14 @@ export function generateBridge(arcCount=1, arcRadius=3,
     const bridgeRoadway = new THREE.Mesh(bridgeRoadwayGeometry, bridgeMaterial);
     bridge.add(bridgeRoadway);
 
+    bridgeRoadway.castShadow    = true;
+    bridgeRoadway.receiveShadow = true;
+
     const cageGeometry = generateBridgeCage(squaresCount)
     cageGeometry.translate(0, bridgeHeight+roadwayHeight-squareTubeRadius*2, 0);
 
     const cageMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
+        side: THREE.FrontSide,
         transparent: false,
         opacity: 1.0,
         shininess: 10,
@@ -282,6 +285,10 @@ export function generateBridge(arcCount=1, arcRadius=3,
     });
 
     const bridgeCage = new THREE.Mesh(cageGeometry, cageMaterial);
+
+    bridgeCage.castShadow    = true;
+    bridgeCage.receiveShadow = true;
+
     bridge.add(bridgeCage);
 
     const roadwayFloorGeometry = new THREE.BoxGeometry(
@@ -298,14 +305,14 @@ export function generateBridge(arcCount=1, arcRadius=3,
     textures.tierra.object.anisotropy = 16;
 
     const roadwayFloorMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        transparent: false,
-        opacity: 1.0,
-        shininess: 10,
+        side: THREE.FrontSide,
         map: textures.tierra.object
     });
 
     const roadwayFloor = new THREE.Mesh(roadwayFloorGeometry, roadwayFloorMaterial);
+    roadwayFloor.receiveShadow = true;
+    roadwayFloor.castShadow = false;
+
     bridge.add(roadwayFloor)
     return bridge;
 }
diff --git a/tp/src/scene.js b/tp/src/scene.js
@@ -3,28 +3,27 @@ import * as dat from 'dat.gui';
 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
 import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
-import { vertexShader, fragmentShader } from '/src/shaders.js';
-
+import Stats from 'three/examples/jsm/libs/stats.module.js';
+import { updateTrainCrankPosition } from '/src/train.js';
 import { generateTunnelGeometry } from '/src/tunnel.js';
 import { createInstancedTrees } from '/src/trees.js';
 import { elevationGeometry } from '/src/terrain.js';
+import { generateBridge } from '/src/bridge.js';
+import { buildTrain } from '/src/train.js';
 import {
     getRailsPathPosAt,
     buildRailsGeometry,
     buildRailsFoundationGeometry
 } from '/src/rails.js';
-import { buildTrain } from '/src/train.js';
-import { generateBridge } from '/src/bridge.js';
-import { updateTrainCrankPosition } from '/src/train.js';
-
-let scene, camera, renderer, terrainGeometry, terrain, time, gui;
-let treesForbiddenMapData, treesForbiddenMap, elevationMap, elevationMapData;
 
+let scene, camera, renderer, time, prevTime, gui, stats;
+let terrainGeometry, terrain, treesForbiddenMapData, treesForbiddenMap, elevationMap, elevationMapData;
 let firstPersonControls, orbitControls;
-
 let train, trainLight, trainLight2, trainLight3;
 
+let helpers = [];
 let cameras = [];
+let camerasName = [];
 let objects = [];
 let lights = {
     ambient:     { object: null },
@@ -38,6 +37,9 @@ let settings = {
     currCameraIndex: 0,
     nightMode: true,
     showHelpers: false,
+    showFps: true,
+    currCameraName: "",
+    shadows: false
 };
 
 let raycaster;
@@ -48,7 +50,6 @@ let moveBackward = false;
 let moveLeft = false;
 let moveRight = false;
 
-let prevTime = performance.now();
 const velocity = new THREE.Vector3();
 const direction = new THREE.Vector3();
 
@@ -94,50 +95,63 @@ function onResize() {
     renderer.setSize( window.innerWidth, window.innerHeight );
 }
 
-function prevCamera() {
-    const camerasCount = cameras.length;
+function updateCamera() {
+    orbitControls.enabled = false;
+    blocker.style.display = 'none';
+    instructions.style.display = 'none';
+    if(settings.nightMode == true) {
+        trainLight.intensity = 200;
+        trainLight.distance = 100;
+    }
+
+    firstPersonControls.unlock();
 
-    if(cameras[settings.currCameraIndex].name == "firstPersonCamera") {
-        firstPersonControls.unlock();
-        blocker.style.display = 'none';
-        instructions.style.display = 'flex';
+    let currCamera = cameras[settings.currCameraIndex];
+    switch(currCamera.name) {
+        case "topView":
+            orbitControls.enabled = true;
+            break;
+        case "firstPersonCamera":
+            blocker.style.display = 'block';
+            instructions.style.display = 'flex';
+            break;
+        case "trainCamera":
+        case "trainConductorCamera":
+            // por alguna razon cuando la camara es `trainConductorCamera`
+            // o `trainCamera` la luz principal del tren se ve mas tenue
+            if(settings.nightMode == true) {
+                trainLight.intensity = 1000;
+                trainLight.distance = 1000;
+            }
+            break;
+        default:
+            break;
     }
+    onResize();
+    settings.currCameraName = camerasName[settings.currCameraIndex];
+}
+
+function prevCamera() {
+    const camerasCount = cameras.length;
 
     if(settings.currCameraIndex == 0) {
         settings.currCameraIndex = (camerasCount - 1);
     } else {
         settings.currCameraIndex -= 1;
     }
-
-    if(cameras[settings.currCameraIndex].name == "firstPersonCamera") {
-        firstPersonControls.unlock();
-        blocker.style.display = 'block';
-        instructions.style.display = 'flex';
-    }
-    onResize();
+    updateCamera();
 }
 
 function nextCamera() {
     const camerasCount = cameras.length;
 
-    if(cameras[settings.currCameraIndex].name == "firstPersonCamera") {
-        firstPersonControls.unlock();
-        blocker.style.display = 'none';
-        instructions.style.display = 'flex';
-    }
-
     if(settings.currCameraIndex == (camerasCount - 1)) {
         settings.currCameraIndex = 0;
     } else {
         settings.currCameraIndex += 1;
     }
 
-    if(cameras[settings.currCameraIndex].name == "firstPersonCamera") {
-        firstPersonControls.unlock();
-        blocker.style.display = 'block';
-        instructions.style.display = 'flex';
-    }
-    onResize();
+    updateCamera();
 }
 
 const blocker = document.getElementById( 'blocker' );
@@ -195,6 +209,9 @@ function keyHandler(event) {
                 } else {
                     nextCamera();
                 }
+                if(gui != undefined) {
+                    gui.__controllers[6].updateDisplay();
+                }
                 break;
             case 'Space':
                 // if (firstPersonControls.isLocked === true) {
@@ -202,7 +219,6 @@ function keyHandler(event) {
                 //     velocity.y += 350;
                 //     break;
                 // }
-                console.log("Toggling train animations");
                 settings.animationEnable = !settings.animationEnable;
                 if(gui != undefined) {
                     // update gui 'Animations' checkbox
@@ -260,6 +276,7 @@ function setupFirstPersonControls() {
     firstPersonCamera.lookAt(-10, 5, 0);
     firstPersonCamera.name = "firstPersonCamera"
     cameras.push(firstPersonCamera);
+    camerasName.push("Primera Persona");
 
     firstPersonControls = new PointerLockControls(firstPersonCamera, document.body);
 
@@ -294,7 +311,15 @@ function setupThreeJs() {
     renderer = new THREE.WebGLRenderer();
     renderer.setPixelRatio( window.devicePixelRatio );
     renderer.setSize( window.innerWidth, window.innerHeight );
-    document.body.appendChild( renderer.domElement );
+    renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
+    renderer.shadowMap.enabled = settings.shadows;
+
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    if(settings.showFps == true) {
+        document.body.appendChild(stats.dom);
+    }
 
     const topView = new THREE.PerspectiveCamera(
         35, window.innerWidth / window.innerHeight, 0.1, 1000);
@@ -303,6 +328,7 @@ function setupThreeJs() {
     topView.lookAt(0, 0, 0);
     topView.name = "topView"
     cameras.push(topView);
+    camerasName.push("Vista Global");
 
     orbitControls = new OrbitControls(topView, renderer.domElement);
 
@@ -311,8 +337,21 @@ function setupThreeJs() {
     lights.hemisphere.object = new THREE.HemisphereLight(0xFFFFFF, 0x000000, 0.25);
 
     lights.directional.object = new THREE.DirectionalLight(0xffffff, 1);
-    lights.directional.object.position.set(-100, 100, 100);
-    
+    lights.directional.object.position.set(-35, 35, 35);
+
+    // Set up shadow properties for the light
+    lights.directional.object.castShadow            = true;
+    lights.directional.object.shadow.mapSize.width  = 512;
+    lights.directional.object.shadow.mapSize.height = 512;
+
+    lights.directional.object.shadow.camera = new THREE.OrthographicCamera(
+        -65, 65, 45, -35, 1.0, 112);
+
+    const directionalLightShadowsHelper = new THREE.CameraHelper(lights.directional.object.shadow.camera);
+    directionalLightShadowsHelper.visible = settings.showHelpers;
+    scene.add(directionalLightShadowsHelper);
+    helpers.push(directionalLightShadowsHelper);
+
     scene.add(lights.ambient.object);
     scene.add(lights.hemisphere.object);
     scene.add(lights.directional.object);
@@ -322,27 +361,32 @@ function setupThreeJs() {
         lights.hemisphere.object.intensity = 0;
         lights.directional.object.color.setHex(0xcdddfe); // 0x090254; 0xa8a1fd
         scene.background = textures.skyNight.object;
-        lights.directional.object.position.set(100, 100, 100); // math the skybox texture moon light
+        lights.directional.object.position.set(35, 35, 35); // match the skybox texture moon light position
     } else {
         lights.ambient.object.visible = true;
         lights.hemisphere.object.intensity = 1;
         lights.directional.object.intensity = 1;
         lights.directional.object.color.setHex(0xFFFFFF);
         scene.background = textures.skyDay.object;
-        lights.directional.object.position.set(-100, 100, 100);
+        lights.directional.object.position.set(-35, 35, 35);
     }
     
-    const helper = new THREE.HemisphereLightHelper(lights.hemisphere.object, 5);
-    if(settings.showHelpers) scene.add(helper) ;
+    const hemisphereLightHelper = new THREE.HemisphereLightHelper(lights.hemisphere.object, 5);
+    helpers.push(hemisphereLightHelper);
 
-    const directinoalLightHelper = new THREE.DirectionalLightHelper( lights.directional.object, 5);
-    if(settings.showHelpers) scene.add(directinoalLightHelper);
+    const directionalLightHelper = new THREE.DirectionalLightHelper(lights.directional.object, 5);
+    helpers.push(directionalLightHelper);
 
-    const gridHelper = new THREE.GridHelper(200, 200);
-    if(settings.showHelpers) scene.add(gridHelper);
+    const gridHelper = new THREE.GridHelper(100, 100);
+    helpers.push(gridHelper);
 
     const axesHelper = new THREE.AxesHelper(5);
-    if(settings.showHelpers) scene.add(axesHelper);
+    helpers.push(axesHelper);
+
+    for(let i = 0; i < helpers.length; ++i) {
+        helpers[i].visible = settings.showHelpers;
+        scene.add(helpers[i]);
+    }
 
     window.addEventListener('resize', onResize);
     onResize();
@@ -410,6 +454,12 @@ function buildBridge() {
     bridge2.add(bridgeCamera);
     bridgeCamera.name = "bridgeCamera";
     cameras.push(bridgeCamera);
+    camerasName.push("Vista del Puente");
+
+    bridge1.castShadow    = true;
+    bridge1.receiveShadow = true;
+    bridge2.castShadow    = true;
+    bridge2.receiveShadow = true;
 
     scene.add(bridge1);
     scene.add(bridge2);
@@ -430,6 +480,7 @@ function buildLoco() {
     train.add(trainConductorCamera);
     trainConductorCamera.name = "trainConductorCamera";
     cameras.push(trainConductorCamera);
+    camerasName.push("Cabina del Tren");
 
     const trainCamera = new THREE.PerspectiveCamera(
         55, window.innerWidth / window.innerHeight, 0.1, 10000);
@@ -439,6 +490,7 @@ function buildLoco() {
     train.add(trainCamera);
     trainCamera.name = `trainCamera`;
     cameras.push(trainCamera);
+    camerasName.push("Costado del Tren");
 
     const trainBackCamera = new THREE.PerspectiveCamera(
         55, window.innerWidth / window.innerHeight, 0.1, 10000);
@@ -448,29 +500,58 @@ function buildLoco() {
     train.add(trainBackCamera);
     trainBackCamera.name = "trainBackCamera";
     cameras.push(trainBackCamera);
+    camerasName.push("Vista hacia atras desde la Cabina del Tren");
 
     // SpotLight(color: Int, intensity: Float, distance: Float, angle: Radians, penumbra: Float, decay: Float)
-    trainLight = new THREE.SpotLight(0xffffff, 100.0, 2000.0, Math.PI/3, 0.5, 0.5);
+    trainLight = new THREE.SpotLight(0xffffff, 200.0, 100.0, Math.PI/6, 0.5, 1.0);
     train.add(trainLight.target);
     train.add(trainLight);
-    trainLight.position.set(0, 2, 15);
-    trainLight.target.position.set(0, -100, 100);
+    trainLight.position.set(0, 4, 5);
+    trainLight.target.position.set(0, -100, 1000);
     trainLight.target.updateMatrixWorld();
 
-    trainLight2 = new THREE.SpotLight(0xffffff, 10.0, 4.0, Math.PI/2, 0.5, 0.5);
+    trainLight2 = new THREE.SpotLight(0xffffff, 10.0, 3.0, Math.PI/6, 0.5, 0.5);
     train.add(trainLight2.target);
     train.add(trainLight2);
-    trainLight2.position.set(0, 5, 20);
+    trainLight2.position.set(0, 3.25, 15);
     trainLight2.target.position.set(0, 0, -100);
     trainLight2.target.updateMatrixWorld();
 
-    trainLight3 = new THREE.SpotLight(0xffffff, 10.0, 10.0, Math.PI/2, 0.5, 0.5);
+    trainLight3 = new THREE.SpotLight(0xffffff, 10.0, 16.0, Math.PI/3, 0.5, 0.5);
     train.add(trainLight3.target);
     train.add(trainLight3);
-    trainLight3.position.set(0, 5, 10);
-    trainLight3.target.position.set(0, 0, 100);
+    trainLight3.position.set(0, 5, 5);
+    trainLight3.target.position.set(0, -25, 100);
     trainLight3.target.updateMatrixWorld();
 
+    //Set up shadow properties for the light
+    trainLight.castShadow            = true;
+    trainLight.shadow.mapSize.width  = 256;
+    trainLight.shadow.mapSize.height = 256;
+    trainLight.shadow.camera.near    = 0.5;
+    trainLight.shadow.camera.far     = 40;
+    trainLight.shadow.focus          = 1;
+
+    trainLight3.castShadow            = true;
+    trainLight3.shadow.mapSize.width  = 128;
+    trainLight3.shadow.mapSize.height = 128;
+    trainLight3.shadow.camera.near    = 0.5;
+    trainLight3.shadow.camera.far     = 25;
+    trainLight3.shadow.focus          = 1;
+
+    const trainLightHelper = new THREE.CameraHelper(trainLight.shadow.camera);
+    const trainLight3Helper = new THREE.CameraHelper(trainLight3.shadow.camera);
+
+    trainLight.visible = settings.nightMode;
+    trainLight2.visible = settings.nightMode;
+    trainLight3.visible = settings.nightMode;
+
+    trainLightHelper.visible  = settings.showHelpers;
+    trainLight3Helper.visible = settings.showHelpers;
+
+    helpers.push(trainLightHelper);
+    helpers.push(trainLight3Helper);
+
     train.scale.set(0.145, 0.145, 0.145);
     train.visible = settings.showTrain;
     scene.add(train);
@@ -485,23 +566,22 @@ function buildRailsFoundation() {
     textures.durmientes.object.anisotropy = 16;
 
     // load into `map` the example texture
-    const map = new THREE.TextureLoader().load(
-        'https://threejs.org/examples/textures/uv_grid_opengl.jpg');
-    map.wrapS = map.wrapT = THREE.RepeatWrapping;
-    map.repeat.set(1, 80);
-    map.anisotropy = 16;
+    // const map = new THREE.TextureLoader().load(
+    //     'https://threejs.org/examples/textures/uv_grid_opengl.jpg');
+    // map.wrapS = map.wrapT = THREE.RepeatWrapping;
+    // map.repeat.set(1, 80);
+    // map.anisotropy = 16;
     // map.rotation = Math.PI/2;
 
     const railsFoundationMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        transparent: false,
-        opacity: 1.0,
-        shininess: 10,
+        side: THREE.FrontSide,
         map: textures.durmientes.object
         // map: map
     });
 
     const railsFoundation = new THREE.Mesh(railsFoundationGeometry, railsFoundationMaterial);
+    railsFoundation.receiveShadow = true;
+    railsFoundation.castShadow    = true;
     railsFoundation.position.set(-1, 1.25, -1);
     railsFoundation.scale.set(1.00, 1.50, 1.00);
     scene.add(railsFoundation);
@@ -513,14 +593,13 @@ function buildRailsFoundation() {
 function buildRails() {
     const railsGeometry = buildRailsGeometry();
     const railsMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        transparent: false,
-        opacity: 1.0,
-        shininess: 10,
+        side: THREE.BackSide,
         color: 0xFFFFFF
     });
 
     const rails = new THREE.Mesh(railsGeometry, railsMaterial);
+    rails.castShadow = true;
+    rails.receiveShadow = true;
     rails.position.set(-1, 1.25, -1);
     rails.scale.set(1.00, 1.50, 1.00);
     scene.add(rails);
@@ -530,9 +609,7 @@ function buildTerrainCustomMaterial() {
     const customMaterial = new THREE.MeshPhongMaterial({
         color: 0xffffff,
         specular: 0x333333,
-        shininess: 10,
-        side: THREE.DoubleSide,
-        reflectivity: 1
+        side: THREE.FrontSide,
     });
 
     // definos las variables uniformes adicionales que necesitamos
@@ -697,6 +774,9 @@ function buildTerrain() {
     const customMaterial = buildTerrainCustomMaterial();
     terrain = new THREE.Mesh(terrainGeometry, customMaterial);
 
+    terrain.castShadow = true;
+    terrain.receiveShadow = true;
+
     scene.add(terrain);
 
     terrain.position.set(0, amplitudeBottom, 0);
@@ -704,10 +784,13 @@ function buildTerrain() {
 
     console.log('Generating water');
     const waterGeometry = new THREE.PlaneGeometry(width/2, height-1.25);
-    const waterMaterial = new THREE.MeshPhongMaterial( {color: 0x12ABFF, side: THREE.DoubleSide} );
+    const waterMaterial = new THREE.MeshPhongMaterial( {color: 0x12ABFF, side: THREE.BackSide} );
     const water = new THREE.Mesh( waterGeometry, waterMaterial );
     water.rotateX(Math.PI/2);
     water.position.set(0, 0, -0.65);
+
+    water.castShadow    = false;
+    water.receiveShadow = true;
     scene.add(water);
 }
 
@@ -721,14 +804,13 @@ function buildTunnel() {
     textures.madera.object.anisotropy = 16;
 
     const tunnelMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        transparent: false,
-        opacity: 1.0,
-        shininess: 10,
+        side: THREE.FrontSide,
         map: textures.madera.object
     });
 
     const tunnel = new THREE.Mesh(tunnelGeometry, tunnelMaterial) ;
+    tunnel.castShadow = true;
+    tunnel.receiveShadow = true;
     tunnel.scale.set(0.5, 0.5, 0.5);
 
     const trainPathPos = getRailsPathPosAt(0.32);
@@ -745,12 +827,18 @@ function buildTunnel() {
     tunnelCamera.name = "tunnelCamera";
     tunnel.add(tunnelCamera);
     cameras.push(tunnelCamera);
+    camerasName.push("Camara del Tunel");
 }
 
 function buildTrees(count = 50) {
     const [treeLogs, treeLeaves] = createInstancedTrees(count);
     scene.add(treeLogs);
     scene.add(treeLeaves);
+
+    treeLogs.castShadow    = true;
+    treeLogs.receiveShadow = true;
+    treeLeaves.castShadow    = true;
+    treeLeaves.receiveShadow = true;
 }
 
 function toggleNightMode() {
@@ -761,7 +849,7 @@ function toggleNightMode() {
         lights.hemisphere.object.intensity = 0;
         lights.directional.object.color.setHex(0xcdddfe); // 0x090254; 0xa8a1fd
         scene.background = textures.skyNight.object;
-        lights.directional.object.position.set(100, 100, 100); // math the skybox texture moon light
+        lights.directional.object.position.set(35, 35, 35); // match the skybox texture moon light
         trainLight.visible = true;
         trainLight2.visible = true;
         trainLight3.visible = true;
@@ -771,7 +859,7 @@ function toggleNightMode() {
         lights.directional.object.intensity = 1;
         lights.directional.object.color.setHex(0xFFFFFF);
         scene.background = textures.skyDay.object;
-        lights.directional.object.position.set(-100, 100, 100);
+        lights.directional.object.position.set(-35, 35, 35);
         trainLight.visible = false;
         trainLight2.visible = false;
         trainLight3.visible = false;
@@ -784,14 +872,48 @@ function createMenu() {
     gui.add(settings, 'showTrain').name('Mostrar tren').onChange(
         function () {
             train.visible = !train.visible;
-        });
+        }
+    );
     gui.add(settings, 'nightMode', false).name('Modo noche').onChange(toggleNightMode);
+    gui.add(settings, 'showHelpers', true).name('Mostrar Guias').onChange(
+        function() {
+            for(let i = 0; i < helpers.length; ++i) {
+                helpers[i].visible = settings.showHelpers;
+                scene.add(helpers[i]);
+            }
+        }
+    );
+    gui.add(settings, 'showFps', true).name('Mostrar FPS').onChange(
+        function() {
+            if(settings.showFps == true) {
+                document.body.appendChild(stats.dom);
+            } else {
+                document.body.removeChild(stats.dom);
+            }
+        }
+    );
+    gui.add(settings, 'shadows', true).name('Sombras').onChange(
+        function() {
+            renderer.shadowMap.enabled = settings.shadows;
+            scene.traverse(function (child) {
+                if (child.material) {
+                    child.material.needsUpdate = true
+                }
+            });
+        }
+    );
+    gui.add(settings, "currCameraName", camerasName).name('Camara').setValue(camerasName[settings.currCameraIndex]).onChange(
+        function() {
+            settings.currCameraIndex = camerasName.indexOf(settings.currCameraName);
+            updateCamera();
+        }
+    );
 }
 
 function buildScene() {
     console.log('Building scene');
     buildTunnel();
-    buildTrees(350);
+    buildTrees(200);
     buildTerrain();
     buildRailsFoundation();
     buildRails();
@@ -800,25 +922,8 @@ function buildScene() {
 }
 
 function mainLoop() {
-    let currCamera = cameras[settings.currCameraIndex];
-    switch(currCamera.name) {
-        case "topView":
-            orbitControls.enabled = true;
-            blocker.style.display = 'none';
-            instructions.style.display = 'none';
-            break;
-        case "firstPersonCamera":
-            orbitControls.enabled = false;
-            break;
-        default:
-            orbitControls.enabled = false;
-            blocker.style.display = 'none';
-            instructions.style.display = 'none';
-            break;
-    }
-
     requestAnimationFrame(mainLoop);
-    renderer.render(scene, currCamera);
+    stats.begin();
 
     const dt = 0.001;
     if(settings.animationEnable) {
@@ -891,16 +996,20 @@ function mainLoop() {
             // canJump = true;
         }
     }
+
     prevTime = time2;
+    renderer.render(scene, cameras[settings.currCameraIndex]);
+    stats.end();
 }
 
 function main() {
     setupThreeJs();
     setupFirstPersonControls();
     time = 0.90;
+    prevTime = performance.now();
     buildScene();
     createMenu();
-    nextCamera();
+    updateCamera();
     mainLoop();
 }
 
diff --git a/tp/src/standalone/rails.js b/tp/src/standalone/rails.js
@@ -172,6 +172,8 @@ export function buildRailsFoundation() {
         map: textures.durmientes.object
     });
     const pMesh = new THREE.Mesh(pGeometry, pMaterial);
+    pMesh.receiveShadow = true;
+    pMesh.castShadow = true;
     scene.add(pMesh);
 }
 
@@ -245,6 +247,7 @@ function buildRails() {
 
     const railsGeometry = mergeGeometries(railsGeometries);
     const rails = new THREE.Mesh(railsGeometry, railsMaterial);
+    rails.castShadow = true;
     scene.add(rails);
 }
 
diff --git a/tp/src/terrain.js b/tp/src/terrain.js
@@ -1,5 +1,4 @@
 import * as THREE from 'three';
-import { vertexShader, fragmentShader } from '/src/shaders.js';
 
 const widthSegments   = 100;
 const heightSegments  = 100;
diff --git a/tp/src/train.js b/tp/src/train.js
@@ -149,19 +149,22 @@ function buildChamber() {
 }
 
 function buildTrainWheel() {
-    const wheel = new THREE.CylinderGeometry(wheelRad, wheelRad, wheelThickness);
-    wheel.rotateZ(Math.PI/2);
+    const wheelGeometry = new THREE.CylinderGeometry(wheelRad, wheelRad, wheelThickness);
+    wheelGeometry.rotateZ(Math.PI/2);
 
     const wheelBolt = new THREE.CylinderGeometry(wheelRad, wheelRad, wheelThickness);
     wheelBolt.rotateZ(Math.PI/2);
 
     const wheelsMaterial = new THREE.MeshPhongMaterial({
         color: 0x393939, 
-        side: THREE.DoubleSide,
+        side: THREE.FrontSide,
         shininess: 100.0
     });
 
-    return new THREE.Mesh(wheel, wheelsMaterial)
+    const wheel = new THREE.Mesh(wheelGeometry, wheelsMaterial);
+    wheel.castShadow = true;
+    wheel.receiveShadow = true;
+    return wheel;
 }
 
 function buildTrainAxe(material) {
@@ -170,7 +173,7 @@ function buildTrainAxe(material) {
 
     const axeMaterial = new THREE.MeshPhongMaterial({
         color: 0x7A7F80, 
-        side: THREE.DoubleSide,
+        side: THREE.FrontSide,
         shininess: 100.0
     });
 
@@ -189,26 +192,32 @@ export function buildTrain() {
     const chassisGeometry = buildTrainChassis();
     const chassisMaterial = new THREE.MeshPhongMaterial({
         color: 0x7A7F80, 
-        side: THREE.DoubleSide,
+        side: THREE.FrontSide,
         shininess: 100.0
     });
 
     const chassis = new THREE.Mesh(chassisGeometry, chassisMaterial);
+    chassis.castShadow = true;
+    chassis.receiveShadow = true;
     train.add(chassis);
 
     const chamberGeometry = buildChamber();
     const chamberMaterial = new THREE.MeshPhongMaterial({
         color: 0xFA1A09, 
-        side: THREE.DoubleSide,
+        side: THREE.FrontSide,
         shininess: 100.0
     });
 
     const chamber = new THREE.Mesh(chamberGeometry, chamberMaterial);
+    chamber.castShadow = true;
+    chamber.receive    = true;
     chassis.add(chamber);
     chamber.position.set(0, (chassisHeight + cabinWallThickness)/2, chassisOffset);
 
     const cabinGeometry = buildCabin();
     const cabin = new THREE.Mesh(cabinGeometry, chamberMaterial);
+    cabin.castShadow = true;
+    cabin.receive = true;
     chassis.add(cabin);
     cabin.position.set(0,
         (chassisHeight + cabinWallThickness)/2,
@@ -217,11 +226,13 @@ export function buildTrain() {
     const cabinRoofGeometry = buildCabinRoof();
     const roofMaterial = new THREE.MeshPhongMaterial({
         color: 0xFBEC50, 
-        side: THREE.DoubleSide,
+        side: THREE.FrontSide,
         shininess: 100.0
     });
 
     const cabinRoof = new THREE.Mesh(cabinRoofGeometry, roofMaterial);
+    cabinRoof.castShadow = true;
+    cabinRoof.receive = true;
     cabin.add(cabinRoof);
     cabinRoof.position.set(0, cabinHeight+cabinRoofHeight+cabinWallThickness/2, 0);
 
@@ -249,11 +260,14 @@ export function buildTrain() {
     const cylindersGeometry = BufferGeometryUtils.mergeGeometries([cylinderRight, cylinderLeft]);
     const cylindersMaterial = new THREE.MeshPhongMaterial({
         color: 0x393939, 
-        side: THREE.DoubleSide,
+        side: THREE.FrontSide,
         shininess: 100.0
     });
 
-    chassis.add(new THREE.Mesh(cylindersGeometry, cylindersMaterial));
+    const cylinders = new THREE.Mesh(cylindersGeometry, cylindersMaterial)
+    cylinders.castShadow = true;
+    cylinders.receiveShadow = true;
+    chassis.add(cylinders);
     chassis.position.set(0,-2,-2.75);
 
     const w1 = buildTrainWheel();
@@ -269,7 +283,7 @@ export function buildTrain() {
     a2.add(w3);
 
     const w4 = buildTrainWheel();
-    w4.position.set(-steamChamberRad+wheelThickness/2.1,0,);
+    w4.position.set(-steamChamberRad+wheelThickness/2.1,0,0);
     a2.add(w4);
 
     const w5 = buildTrainWheel();
@@ -300,7 +314,7 @@ export function buildTrain() {
 
     const lightMaterial = new THREE.MeshPhongMaterial({
         color: 0x393939, 
-        side: THREE.DoubleSide,
+        side: THREE.FrontSide,
         shininess: 100.0,
         emissive: 0xf6d32d
     });
diff --git a/tp/src/trees.js b/tp/src/trees.js
@@ -80,7 +80,7 @@ export function createInstancedTrees(count) {
     treeLogGeometry.translate(0, logHeight/2.0, 0);
     const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry();
     instancedTreeLogGeometry.copy(treeLogGeometry);
-    const treeLogMaterial   = new THREE.MeshPhongMaterial({color: 0x7c3f00});
+    const treeLogMaterial   = new THREE.MeshPhongMaterial({color: 0x7c3f00, side: THREE.FrontSide});
     const instancedTreeLogs = new THREE.InstancedMesh(
         instancedTreeLogGeometry,
         treeLogMaterial,