TA159

Notas, resueltos y trabajos practicos de la materia Sistemas Gráficos
Index Commits Files Refs Submodules README LICENSE
commit 54c80d2d07e11b69c59002e21b05d5518f185076
parent 1287c5fbc3f17fc02584765d9afa29915c48a348
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date:   Thu, 27 Jun 2024 19:42:31 -0300

make all elements in scene a module an import them in `scene.js`

Diffstat:
Mtp/src/bridge.js | 72+++++++++++-------------------------------------------------------------
Mtp/src/rails.js | 176++++++++++++++++++++++---------------------------------------------------------
Mtp/src/scene.js | 430+++++++++++++++++++++++++------------------------------------------------------
Mtp/src/terrain.js | 213+++----------------------------------------------------------------------------
Mtp/src/track-map.js | 288++++++++++++++++++++++++++++++-------------------------------------------------
Mtp/src/train.js | 89++++++-------------------------------------------------------------------------
Mtp/src/tunnel.js | 128+++----------------------------------------------------------------------------
7 files changed, 321 insertions(+), 1075 deletions(-)
diff --git a/tp/src/bridge.js b/tp/src/bridge.js
@@ -1,56 +1,11 @@
 import * as THREE from 'three';
-import * as dat from 'dat.gui';
-import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
 
-let scene, camera, renderer, container;
-
 const textures = {
     tierra:     { url: '/assets/tierraSeca.jpg', object: null },
     ladrillos:  { url: '/assets/pared-de-ladrillos.jpg', object: null },
 };
 
-function onResize() {
-    camera.aspect = container.offsetWidth / container.offsetHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-}
-
-function setupThreeJs() {
-    scene = new THREE.Scene();
-    container = document.getElementById('mainContainer');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setClearColor(0x606060);
-    // renderer.setClearColor(0xFFFFFF);
-    container.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 1000);
-    camera.position.set(25, 25, 25);
-    camera.lookAt(10, 10, 10);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-
-    const ambientLight = new THREE.AmbientLight(0xaaaaaa);
-    scene.add(ambientLight);
-
-    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
-    scene.add(hemisphereLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
-    directionalLight.position.set(100, 100, 100);
-    scene.add(directionalLight);
-
-    const gridHelper = new THREE.GridHelper(50, 20);
-    scene.add(gridHelper);
-
-    const axesHelper = new THREE.AxesHelper(5);
-    scene.add(axesHelper);
-
-    window.addEventListener('resize', onResize);
-    onResize();
-}
-
 function onTextureLoaded(key, texture) {
     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
     textures[key].object = texture;
@@ -92,7 +47,7 @@ const bridgeWallThickness = 2.5;
 const bridgeLen           = arcCount*(columnWidth+arcWidth)+columnWidth+startPadding+endPadding;
 const bridgeHeight        = columnHeight+arcRadius+topPadding;
 
-function generateBridgeWall() {
+export function generateBridgeWallGeometry() {
     const path = new THREE.Path();
 
     // generate the arcs
@@ -227,14 +182,16 @@ function generateBridgeCage(squaresCount = 3) {
     return bridgeCage;
 }
 
-function generateBridge() {
+export function generateBridge() {
+    const bridge = new THREE.Object3D();
+
     const bridgeWidth   = 10;
     const roadwayHeight = 2;
 
-    const leftWallGeometry = generateBridgeWall();
+    const leftWallGeometry = generateBridgeWallGeometry();
     leftWallGeometry.translate(0, 0, -bridgeWidth/2);
 
-    const rightWallGeometry = generateBridgeWall();
+    const rightWallGeometry = generateBridgeWallGeometry();
     rightWallGeometry.translate(0, 0, bridgeWidth/2)
 
     const bridgeColumnsGeometry = mergeGeometries([leftWallGeometry, rightWallGeometry]);
@@ -277,7 +234,7 @@ function generateBridge() {
     */
 
     const bridgeColumns = new THREE.Mesh(bridgeColumnsGeometry, bridgeMaterial);
-    scene.add(bridgeColumns);
+    bridge.add(bridgeColumns);
 
     // para reutilizar la textura de ladrillos usada en los arcos se escalan las
     // coordenadas uv de la geometria de la parte superior
@@ -287,7 +244,7 @@ function generateBridge() {
     }
 
     const bridgeRoadway = new THREE.Mesh(bridgeRoadwayGeometry, bridgeMaterial);
-    scene.add(bridgeRoadway);
+    bridge.add(bridgeRoadway);
 
     const cageGeometry = generateBridgeCage()
     cageGeometry.translate(0, bridgeHeight+roadwayHeight-squareTubeRadius*2, 0);
@@ -301,7 +258,7 @@ function generateBridge() {
     });
 
     const bridgeCage = new THREE.Mesh(cageGeometry, cageMaterial);
-    scene.add(bridgeCage);
+    bridge.add(bridgeCage);
 
     const roadwayFloorGeometry = new THREE.PlaneGeometry(
         bridgeWidth+bridgeWallThickness,
@@ -325,18 +282,11 @@ function generateBridge() {
     });
 
     const roadwayFloor = new THREE.Mesh(roadwayFloorGeometry, roadwayFloorMaterial);
-    scene.add(roadwayFloor)
-}
-
-function mainLoop() {
-    requestAnimationFrame(mainLoop);
-    renderer.render(scene, camera);
+    bridge.add(roadwayFloor)
+    return bridge;
 }
 
 function main() {
-    generateBridge();
-    mainLoop();
 }
 
-setupThreeJs();
 loadTextures(main);
diff --git a/tp/src/rails.js b/tp/src/rails.js
@@ -1,15 +1,19 @@
 import * as THREE from 'three';
-import * as dat from 'dat.gui';
-import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 
 import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
 import { ParametricGeometries } from 'three/examples/jsm/geometries/ParametricGeometries.js';
 import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
 
-let scene, camera, renderer, container, terrainMaterial, instancedTrees;
-let spherePath;
 let railsPath;
-let railsFoundationShape;
+
+export const railsFoundationShape = new THREE.CatmullRomCurve3([
+    new THREE.Vector3( -2.00, 0.00, 0.00),
+    new THREE.Vector3( -1.00, 0.00, 0.50),
+    new THREE.Vector3(  0.00, 0.00, 0.55),
+    new THREE.Vector3(  1.00, 0.00, 0.50),
+    new THREE.Vector3(  2.00, 0.00, 0.00),
+], false);
+
 
 const textures = {
     tierra:     { url: '/assets/tierra.jpg', object: null },
@@ -18,77 +22,6 @@ const textures = {
     durmientes: { url: '/assets/durmientes.jpg', object: null },
 };
 
-function onResize() {
-    camera.aspect = container.offsetWidth / container.offsetHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-}
-
-function setupThreeJs() {
-    scene = new THREE.Scene();
-    container = document.getElementById('mainContainer');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setClearColor(0x606060);
-    container.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(
-        35, window.innerWidth / window.innerHeight, 0.1, 1000);
-
-    camera.position.set(-10, 15, -10);
-    camera.lookAt(0, 0, 0);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
-    scene.add(hemisphereLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
-    directionalLight.position.set(100, 100, 100);
-    scene.add(directionalLight);
-
-    const gridHelper = new THREE.GridHelper(50, 20);
-    scene.add(gridHelper);
-
-    const axesHelper = new THREE.AxesHelper(5);
-    scene.add(axesHelper);
-
-    window.addEventListener('resize', onResize);
-    onResize();
-}
-
-function onTextureLoaded(key, texture) {
-    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
-    textures[key].object = texture;
-    console.log('Texture `' + key + '` loaded');
-}
-
-function loadTextures(callback) {
-    const loadingManager = new THREE.LoadingManager();
-
-    loadingManager.onLoad = () => {
-        console.log('All textures loaded');
-        callback();
-    };
-
-    for (const key in textures) {
-        console.log("Loading textures");
-        const loader = new THREE.TextureLoader(loadingManager);
-        const texture = textures[key];
-        texture.object = loader.load(
-            texture.url,
-            onTextureLoaded.bind(this, key),
-            null,
-            (error) => {
-                console.error(error);
-            }
-        );
-    }
-}
-
 function parametricRailsFoundationFunction(u, v, target) {
     const rotMatrix = new THREE.Matrix4();
     const translationMatrix = new THREE.Matrix4();
@@ -125,53 +58,26 @@ function parametricRailsFoundationFunction(u, v, target) {
     target.set(x, y, z);
 }
 
-function buildRailsFoundation() {
-    railsFoundationShape = new THREE.CatmullRomCurve3([
-        new THREE.Vector3( -2.00, 0.00, 0.00),
-        new THREE.Vector3( -1.00, 0.00, 0.50),
-        new THREE.Vector3(  0.00, 0.00, 0.55),
-        new THREE.Vector3(  1.00, 0.00, 0.50),
-        new THREE.Vector3(  2.00, 0.00, 0.00),
-    ], false);
-
+// devuelve la geometria del terraplen de la via
+export function buildRailsFoundationGeometry() {
+    /*
     // show rails foundation shape
     const points = railsFoundationShape.getPoints(50);
     const geometry = new THREE.BufferGeometry().setFromPoints(points);
     const lineMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 });
     const curveObject = new THREE.Line(geometry, lineMaterial);
     scene.add(curveObject);
+    */
 
     const pGeometry = new ParametricGeometry(
         parametricRailsFoundationFunction, 100, 100);
     
-    textures.durmientes.object.wrapS = THREE.RepeatWrapping;
-    textures.durmientes.object.wrapT = THREE.RepeatWrapping;
-    textures.durmientes.object.repeat.set(1, 60);
-    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, 30);
-    map.anisotropy = 16;
-    // map.rotation = Math.PI/2;
-    */
-
-    const pMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        transparent: false,
-        opacity: 1.0,
-        shininess: 10,
-        map: textures.durmientes.object
-    });
-    const pMesh = new THREE.Mesh(pGeometry, pMaterial);
-    scene.add(pMesh);
+    return pGeometry;
 }
 
 // `position` es de tipo `THREE.Vector3` y representa la translacion de la
 // forma del rail con respecto al origen del sist. de coordenadas de modelado
-function getParametricRailsFunction(radius, position) {
+function getParametricRailsFunction(railsRadius = 0.50, position) {
     return function parametricRails(u, v, target) {
         const rotMatrix = new THREE.Matrix4();
         const translationMatrix = new THREE.Matrix4();
@@ -213,8 +119,8 @@ function getParametricRailsFunction(radius, position) {
     }
 }
 
-const railsRadius = 0.35;
-function buildRails() {
+// devuelve la geometria de los rieles
+export function buildRailsGeometry(railsRadius = 0.35) {
     let railsGeometries = [];
 
     const leftRailGeometryFunction  = getParametricRailsFunction(railsRadius,
@@ -229,22 +135,37 @@ function buildRails() {
     railsGeometries.push(leftRailGeometry);
     railsGeometries.push(rightRailGeometry);
 
-    const railsMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        transparent: false,
-        opacity: 1.0,
-        shininess: 10,
-        color: 0xFFFFFF
-    });
-
     const railsGeometry = mergeGeometries(railsGeometries);
-    const rails = new THREE.Mesh(railsGeometry, railsMaterial);
-    scene.add(rails);
+    return railsGeometry;
+}
+
+function onTextureLoaded(key, texture) {
+    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+    textures[key].object = texture;
+    console.log('Texture `' + key + '` loaded');
 }
 
-function mainLoop() {
-    requestAnimationFrame(mainLoop);
-    renderer.render(scene, camera);
+function loadTextures(callback) {
+    const loadingManager = new THREE.LoadingManager();
+
+    loadingManager.onLoad = () => {
+        console.log('All textures loaded');
+        callback();
+    };
+
+    for (const key in textures) {
+        console.log("Loading textures");
+        const loader = new THREE.TextureLoader(loadingManager);
+        const texture = textures[key];
+        texture.object = loader.load(
+            texture.url,
+            onTextureLoaded.bind(this, key),
+            null,
+            (error) => {
+                console.error(error);
+            }
+        );
+    }
 }
 
 function main() {
@@ -264,10 +185,9 @@ function main() {
     scene.add(railsPathMesh);
     */
 
-    buildRailsFoundation();
-    buildRails();
-    mainLoop();
+    // buildRailsFoundation();
+    // buildRails();
 }
 
-setupThreeJs();
+// setupThreeJs();
 loadTextures(main);
diff --git a/tp/src/scene.js b/tp/src/scene.js
@@ -3,7 +3,18 @@ import * as dat from 'dat.gui';
 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 import { vertexShader, fragmentShader } from '/assets/shaders.js';
 
-let scene, camera, renderer, container, terrainMaterial, terrainGeometry, terrain;
+import { generateTunnelGeometry } from '/src/tunnel.js';
+import { createInstancedTrees } from '/src/track-map.js';
+import { elevationGeometry } from '/src/terrain.js';
+import { 
+    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, container, terrainMaterial, terrainGeometry, terrain, time;
 let treesForbiddenMapData, treesForbiddenMap, elevationMap, elevationMapData;
 
 const widthSegments   = 100;
@@ -12,13 +23,19 @@ const amplitude       = 8;
 const amplitudeBottom = -1.00;
 
 const textures = {
-    tierra:           { url: '/assets/tierra.jpg', object: null },
     roca:             { url: '/assets/roca.jpg', object: null },
     pasto:            { url: '/assets/pasto.jpg', object: null },
+    tierra:           { url: '/assets/tierra.jpg', object: null },
+    madera:           { url: '/assets/madera.jpg', object: null },
+    durmientes:       { url: '/assets/durmientes.jpg', object: null },
     elevationMap:     { url: '/assets/elevation_map2.png', object: null },
     treeForbiddenMap: { url: '/assets/tree_forbidden_zone_map.png', object: null }
 };
 
+let settings = {
+    animationEnable: true,
+};
+
 function onResize() {
     camera.aspect = container.offsetWidth / container.offsetHeight;
     camera.updateProjectionMatrix();
@@ -33,9 +50,8 @@ function setupThreeJs() {
     renderer.setClearColor(0x606060);
     container.appendChild(renderer.domElement);
 
-    camera = new THREE.PerspectiveCamera(
-        35, window.innerWidth/window.innerHeight, 0.1, 1000);
-    camera.position.set(100, 120, -100);
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 1000);
+    camera.position.set(-50, 60, 50);
     camera.lookAt(0, 0, 0);
 
     const controls = new OrbitControls(camera, renderer.domElement);
@@ -44,266 +60,110 @@ function setupThreeJs() {
     scene.add(ambientLight);
 
     const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
-    //scene.add(hemisphereLight);
+    scene.add(hemisphereLight);
 
     const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
     directionalLight.position.set(100, 100, 100);
     scene.add(directionalLight);
 
-    const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
-    // scene.add(directionalLightHelper);
+    const gridHelper = new THREE.GridHelper(150, 150);
+    scene.add(gridHelper);
 
-     const gridHelper = new THREE.GridHelper(150, 150);
-     scene.add(gridHelper);
-
-    const axesHelper = new THREE.AxesHelper( 5 );
-    scene.add( axesHelper );
+    const axesHelper = new THREE.AxesHelper(5);
+    scene.add(axesHelper);
 
     window.addEventListener('resize', onResize);
     onResize();
 }
 
-const imgWidth  = 512;
-const imgHeight = 512;
-
-// (x, y) ∈ [imgHeight, imgWidth] -> son un punto de la imagen 
-function getPixelIndex(x, y) {
-    return Math.floor(x + y*imgWidth*4);
-}
-
-function getPixel(imgData, index) {
-  let i = index*4, d = imgData.data
-  return [d[i],d[i+1],d[i+2],d[i+3]] // Returns array [R,G,B,A]
+function onTextureLoaded(key, texture) {
+    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+    textures[key].object = texture;
+    console.log('Texture `' + key + '` loaded');
 }
 
-function getPixelXY(imgData, x, y) {
-  return getPixel(imgData, y*imgData.width+x)
-}
+function loadTextures(callback) {
+    const loadingManager = new THREE.LoadingManager();
 
-// position: Vector3
-function isForbbidenPosition(position) {
-    const x = Math.floor(position.x);
-    const y = position.y;
-    const z = Math.floor(position.z);
+    loadingManager.onLoad = () => {
+        console.log('All textures loaded');
+        callback();
+    };
 
-    /*
-    if((y > 5.0) || (y < 2.65)){
-        console.log("(" + position.x + ", " + position.y + ", " + position.z + ") is not valid ");
-        return true;
-    }
-    */
-    
-    let pixelArray = getPixelXY(treesForbiddenMap, x, z);
-    const R = pixelArray[0]; // Red
-    const G = pixelArray[1]; // Green
-    const B = pixelArray[2]; // Blue
-    const A = pixelArray[3]; // Alpha
-    // const pixel = new THREE.Vector4(R, G, B, A);
-
-    if(((R <= 10) && (G >= 250) && (B <= 10))
-        || (R <= 80) && (G <= 80) && (B <= 80)
-        || (R >= 200) && (G >= 200) && (B >= 200)) {
-        // console.log("(" + position.x + ", " + position.y + ", " + position.z + ") is not valid ");
-        return true;
+    for (const key in textures) {
+        console.log("Loading textures");
+        const loader = new THREE.TextureLoader(loadingManager);
+        const texture = textures[key];
+        texture.object = loader.load(
+            texture.url,
+            onTextureLoaded.bind(this, key),
+            null,
+            (error) => {
+                console.error(error);
+            }
+        );
     }
-
-    console.log("(" + position.x + ", " + position.y + ") is valid ");
-    return false;
 }
 
-// obtiene una posicion aleatoria en el terreno, para obtener la altura del
-// terreno utiliza el mapa de elevacion.
-// `padding` permite definir un borde del cual no se toman puntos
-function getRandomPositionInTerrain(padding = 0) {
-    const x = Math.floor(Math.random() * (widthSegments-(padding*2)));
-    const z = Math.floor(Math.random() * (heightSegments-(padding*2)));
-
-    const pixelArray = getPixelXY(elevationMap, x, z); // array [R,G,B,A]
-    const y = (pixelArray[0]/255)*amplitude;
-
-    const position = new THREE.Vector3(x+padding, y, z+padding);
-    return position;
+function buildBridge() {
+    const bridge = generateBridge();
+    scene.add(bridge);
 }
 
-function createInstancedTrees(count) {
-    console.log('Generating `' + count + '` instances of tree');
-
-    let logHeight = 4.0;
-    const treeLogGeometry   = new THREE.CylinderGeometry(
-        0.30, 0.30, logHeight, 40, 40);
-    treeLogGeometry.translate(0, logHeight/2.0, 0);
-    const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry();
-    instancedTreeLogGeometry.copy(treeLogGeometry);
-    const treeLogMaterial   = new THREE.MeshPhongMaterial({color: 0x7c3f00});
-    const instancedTreeLogs = new THREE.InstancedMesh(
-        instancedTreeLogGeometry,
-        treeLogMaterial,
-        count);
-
-    const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
-    const instancedTreeLeavesGeometry = new THREE.InstancedBufferGeometry();
-    instancedTreeLeavesGeometry.copy(treeLeavesGeometry);
-    const treeLeavesMaterial  = new THREE.MeshPhongMaterial({color: 0x365829});
-    const instancedTreeLeaves = new THREE.InstancedMesh(
-        instancedTreeLeavesGeometry,
-        treeLeavesMaterial,
-        count);
-
-    const rotMatrix         = new THREE.Matrix4();
-    const translationMatrix = new THREE.Matrix4();
-    const treeLogMatrix     = new THREE.Matrix4();
-    const treeLeavesMatrix  = new THREE.Matrix4();
-
-    const treesBorderPadding = 3.0;
-    for (let i = 0; i < count; i++) {
-        let position = getRandomPositionInTerrain(treesBorderPadding);
-        for(let j = 0; isForbbidenPosition(position); ++j) {
-            position = getRandomPositionInTerrain(treesBorderPadding);
-            if(j++ == 1000) { // maximo de iteraciones
-                break;
-            }
-        }
-
-        if(isForbbidenPosition(position)) {
-            continue;
-        }
-
-        position.x -= widthSegments/2;
-        position.y += amplitudeBottom;
-        position.z -= heightSegments/2;
-        translationMatrix.makeTranslation(position);
-        treeLogMatrix.identity();
-        treeLeavesMatrix.identity();
-
-        let scale = 0.5 + (Math.random()*(logHeight/3));
-        treeLogMatrix.makeScale(1, scale, 1);
-        treeLogMatrix.premultiply(translationMatrix);
-
-        position.y += scale * logHeight;
-        translationMatrix.makeTranslation(position);
-        treeLeavesMatrix.premultiply(translationMatrix);
-
-        instancedTreeLogs.setMatrixAt(i, treeLogMatrix);
-        instancedTreeLeaves.setMatrixAt(i, treeLeavesMatrix);
-    }
-
-    console.log('Done generating `' + count + '` instances of tree');
-    return [instancedTreeLogs, instancedTreeLeaves];
+// loco -> locomotora/locomotive
+function buildLoco() {
+    const train = buildTrain();
+    train.scale.set(0.35, 0.35, 0.35)
+    scene.add(train);
 }
 
+function buildRailsFoundation() {
+    const railsFoundationGeometry = buildRailsFoundationGeometry();
 
-// La funcion devuelve una geometria de Three.js
-// width: Ancho del plano
-// height: Alto del plano
-// amplitude: Amplitud de la elevacion
-// widthSegments: Numero de segmentos en el ancho
-// heightSegments: Numero de segmentos en el alto
-function elevationGeometry(width, height, amplitude, widthSegments, heightSegments) {
-    console.log('Generating terrain geometry');
-    let geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const indices = [];
-    const normals = [];
-    const uvs = [];
-
-    let imageData = elevationMap;
-    let data = elevationMapData;
-
-    const quadsPerRow = widthSegments - 1;
-
-    // Recorremos los segmentos horizontales y verticales
-    for (let x = 0; x < widthSegments - 1; x++) {
-        for (let y = 0; y < heightSegments - 1; y++) {
-            // valor del pixel en el mapa de elevacion en la posicion i, j
-
-            let pixel = getPixelXY(imageData, x, y);
-
-            // se utiliza el canal rojo [R, G, B, A];
-            let z0 = pixel[0] / 255;
-            const z = amplitude * z0;
-
-            // valores de los píxeles de los puntos adyacentes
-            let xPrev, xNext, yPrev, yNext;
-
-            xPrev = (x > 0) ? getPixelXY(imageData, x-1, y)[0]/255 : undefined;
-            xNext = (x < widthSegments-1) ? getPixelXY(imageData, x+1, y)[0]/255 : undefined;
-
-            yPrev = (y > 0) ? getPixelXY(imageData, x, y+1)[0]/255 : undefined;
-            yNext = (y < heightSegments-1) ? getPixelXY(imageData, x, y+1)[0]/255 : undefined;
-
-            // diferencia entre los valores de los píxeles adyacentes en el eje
-            // `x` y en el eje `y` de la imagen en el espacio de la textura
-            let deltaX;
-            if (xPrev == undefined) {
-                deltaX = xNext - z0;
-            } else if (yNext == undefined) {
-                deltaX = xPrev - z0;
-            } else {
-                deltaX = (xNext - xPrev) / 2;
-            }
-
-            let deltaY;
-            if (yPrev == undefined) {
-                deltaY = yNext - z0;
-            } else if (yNext == undefined) {
-                deltaY = yPrev - z0;
-            } else {
-                deltaY = (yNext - yPrev) / 2;
-            }
-
-            // Añadimos los valores de los puntos al array de posiciones
-            positions.push((width * x) / widthSegments - width / 2);
-            positions.push(z);
-            positions.push((height * y) / heightSegments - height / 2);
-
-            // vectores tangentes a la superficie en el eje `x` y en el eje `y`
-            let tanX = new THREE.Vector3(width / widthSegments, deltaX * amplitude, 0);
-            let tanY = new THREE.Vector3(0, deltaY * amplitude, height / heightSegments);
-
-            tanX.normalize();
-            tanY.normalize();
+    textures.durmientes.object.wrapS = THREE.RepeatWrapping;
+    textures.durmientes.object.wrapT = THREE.RepeatWrapping;
+    textures.durmientes.object.repeat.set(1, 60);
+    textures.durmientes.object.anisotropy = 16;
 
-            let normal = new THREE.Vector3().crossVectors(tanY, tanX);
-
-            normals.push(normal.x);
-            normals.push(normal.y);
-            normals.push(normal.z);
-
-            uvs.push(x / (widthSegments - 1));
-            uvs.push(y / (heightSegments - 1));
-
-            if ((x == widthSegments-2) || (y == heightSegments-2)) continue;
-
-            // Ensamblamos los triangulos
-            indices.push(x + y*quadsPerRow);
-            indices.push(x + 1 + y*quadsPerRow);
-            indices.push(x + 1 + (y+1)*quadsPerRow);
+    const railsFoundationMaterial = new THREE.MeshPhongMaterial({
+        side: THREE.DoubleSide,
+        transparent: false,
+        opacity: 1.0,
+        shininess: 10,
+        map: textures.durmientes.object
+    });
 
-            indices.push(x + y*quadsPerRow);
-            indices.push(x + 1 + (y+1)*quadsPerRow);
-            indices.push(x + (y+1)*quadsPerRow);
-        }
-    }
+    const railsFoundation = new THREE.Mesh(railsFoundationGeometry, railsFoundationMaterial);
+    railsFoundation.scale.set(5, 5, 5);
+    scene.add(railsFoundation);
+}
 
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-    geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
-    geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
-    geometry.setIndex(indices);
+function buildRails() {
+    const railsGeometry = buildRailsGeometry();
+    const railsMaterial = new THREE.MeshPhongMaterial({
+        side: THREE.DoubleSide,
+        transparent: false,
+        opacity: 1.0,
+        shininess: 10,
+        color: 0xFFFFFF
+    });
 
-    return geometry;
+    const rails = new THREE.Mesh(railsGeometry, railsMaterial);
+    rails.scale.set(5, 5, 5);
+    scene.add(rails);
 }
 
-function buildScene() {
-    console.log('Building scene');
+function buildTerrain() {
+    // console.log('Building scene');
 
-    const width  = widthSegments;
-    const height = heightSegments;
+    const width = 100;
+    const height = 100;
 
     terrainGeometry = elevationGeometry(
         width, height,
         amplitude,
-        widthSegments, heightSegments);
+        widthSegments, heightSegments,
+        textures.elevationMap.object);
 
     console.log('Applying textures');
     terrainMaterial = new THREE.RawShaderMaterial({
@@ -328,91 +188,73 @@ function buildScene() {
     terrain.position.set(0, amplitudeBottom, 0);
     scene.add(terrain);
 
-    const waterGeometry = new THREE.PlaneGeometry(width/2, height-1.55);
+    console.log('Generating water');
+    const waterGeometry = new THREE.PlaneGeometry(width/2, height);
     const waterMaterial = new THREE.MeshPhongMaterial( {color: 0x12ABFF, side: THREE.DoubleSide} );
     const water = new THREE.Mesh( waterGeometry, waterMaterial );
     water.rotateX(Math.PI/2);
-    water.position.set(0, 0.75, -1.00);
+    water.position.set(0, 0.75, 0);
     scene.add(water);
-
-    const [treeLogs, treeLeaves] = createInstancedTrees(250);
-    scene.add(treeLogs);
-    scene.add(treeLeaves);
 }
 
-function onTextureLoaded(key, texture) {
-    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
-    textures[key].object = texture;
-    console.log('Texture `' + key + '` loaded');
-}
+function buildTunnel() {
+    const tunnelGeometry = generateTunnelGeometry();
 
-function loadTextures(callback) {
-    const loadingManager = new THREE.LoadingManager();
+    textures.madera.object.wrapS = THREE.RepeatWrapping;
+    textures.madera.object.wrapT = THREE.RepeatWrapping;
+    textures.madera.object.repeat.set(0.10, 0.10);
+    textures.madera.object.anisotropy = 16;
 
-    loadingManager.onLoad = () => {
-        console.log('All textures loaded');
-        callback();
-    };
+    const tunnelMaterial = new THREE.MeshPhongMaterial({
+        side: THREE.DoubleSide,
+        transparent: false,
+        opacity: 1.0,
+        shininess: 10,
+        map: textures.madera.object
+    });
 
-    for (const key in textures) {
-        console.log("Loading textures");
-        const loader = new THREE.TextureLoader(loadingManager);
-        const texture = textures[key];
-        texture.object = loader.load(
-            texture.url,
-            onTextureLoaded.bind(this, key),
-            null,
-            (error) => {
-                console.error(error);
-            }
-        );
-    }
+    const mesh = new THREE.Mesh(tunnelGeometry, tunnelMaterial) ;
+    scene.add(mesh);
+}
+
+function buildTrees(count = 50) {
+    const [treeLogs, treeLeaves] = createInstancedTrees(count);
+    scene.add(treeLogs);
+    scene.add(treeLeaves);
 }
 
 function createMenu() {
-    const gui = new dat.GUI({ width: 400 });
-    gui.add(terrainMaterial.uniforms.scale, 'value', 1.00, 5.00).name('Terrain texture scale');
-    gui.add(terrainMaterial.uniforms.dirtStepWidth, 'value', 0.0, 1.0).name('dirt step width');
-    gui.add(terrainMaterial.uniforms.rockStepWidth, 'value', 0.10, 0.50).name('rock step width');
+    const gui = new dat.GUI({ width: 250 });
+    gui.add(settings, 'animationEnable', true).name('Animations enabled');
+    // console.log(settings.animationEnable);
+}
+
+function buildScene() {
+    console.log('Building scene');
+    buildTunnel();
+    // buildTrees(100);
+    // buildTerrain();
+    buildRailsFoundation();
+    buildRails();
+    buildLoco();
+    // buildBridge();
 }
 
 function mainLoop() {
     requestAnimationFrame(mainLoop);
     renderer.render(scene, camera);
-}
-
-function loadMapsData() {
-    console.log("Loading maps data");
-
-    // Creamos un canvas para poder leer los valores de los píxeles de la textura
-    let canvas = document.createElement('canvas');
-    let ctx = canvas.getContext('2d');
 
-    let treesForbiddenMapImage = textures.treeForbiddenMap.object.image;
-    let elevationMapImage = textures.elevationMap.object.image;
-
-    // ambos mapas deben tener el mismo tamaño
-    const imgWidth  = widthSegments;
-    const imgHeight = heightSegments;
-
-    canvas.width  = imgWidth;
-    canvas.height = imgHeight;
-
-    ctx.drawImage(treesForbiddenMapImage, 0, 0, imgWidth, imgHeight);
-    treesForbiddenMap = ctx.getImageData(0, 0, imgWidth, imgHeight);
-    treesForbiddenMapData = treesForbiddenMap.data;
-
-    ctx.drawImage(elevationMapImage, 0, 0, imgWidth, imgHeight);
-    elevationMap = ctx.getImageData(0, 0, imgWidth, imgHeight);
-    elevationMapData = elevationMap.data
-
-    console.log("All maps data loaded succesfully");
+    time += 0.05;
+    if(settings.animationEnable) {
+        updateTrainCrankPosition(time);
+    }
+    renderer.render(scene, camera);
 }
 
 function main() {
-    loadMapsData();
+    time = 0.00;
     buildScene();
-    // getTrainTrackShape();
+    createMenu();
     mainLoop();
 }
 
diff --git a/tp/src/terrain.js b/tp/src/terrain.js
@@ -1,13 +1,11 @@
 import * as THREE from 'three';
-import * as dat from 'dat.gui';
-import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 import { vertexShader, fragmentShader } from '/assets/shaders.js';
 
-let scene, camera, renderer, container, terrainMaterial, terrainGeometry, terrain;
+let terrainMaterial, terrainGeometry, terrain;
 
-const widthSegments = 100;
-const heightSegments = 100;
-const amplitude = 8;
+const widthSegments   = 100;
+const heightSegments  = 100;
+const amplitude       = 8;
 const amplitudeBottom = -1.00;
 
 const textures = {
@@ -17,141 +15,6 @@ const textures = {
     elevationMap: { url: '/assets/elevation_map2.png', object: null },
 };
 
-function onResize() {
-    camera.aspect = container.offsetWidth / container.offsetHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-}
-
-function setupThreeJs() {
-    scene = new THREE.Scene();
-    container = document.getElementById('mainContainer');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setClearColor(0x606060);
-    container.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(
-        35, window.innerWidth/window.innerHeight, 0.1, 1000);
-    camera.position.set(100, 120, -100);
-    camera.lookAt(0, 0, 0);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
-    //scene.add(hemisphereLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
-    directionalLight.position.set(100, 100, 100);
-    scene.add(directionalLight);
-
-    const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
-    // scene.add(directionalLightHelper);
-
-     const gridHelper = new THREE.GridHelper(150, 150);
-     scene.add(gridHelper);
-
-    const axesHelper = new THREE.AxesHelper( 5 );
-    scene.add( axesHelper );
-
-    window.addEventListener('resize', onResize);
-    onResize();
-}
-
-// obtiene una posicion aleatoria en el terreno, para obtener la altura del
-// terreno utiliza el mapa de elevacion
-function getRandomPositionInTerrain() {
-    let canvas = document.createElement('canvas');
-    let ctx = canvas.getContext('2d');
-    let img = textures.elevationMap.object.image;
-
-    canvas.width  = widthSegments;
-    canvas.height = heightSegments;
-
-    ctx.drawImage(img, 0, 0, widthSegments, heightSegments);
-    let imageData = ctx.getImageData(0, 0, widthSegments, heightSegments);
-    let data = imageData.data;
-    const quadsPerRow = widthSegments - 1;
-
-    const x = Math.random();
-    const z = Math.random();
-
-    const elevationMapData = Math.floor((x + z) * widthSegments);
-    const indexX = Math.floor(x * widthSegments);
-    const indexZ = Math.floor(z * heightSegments);
-    const y = data[(indexX + indexZ * widthSegments) * 4] / 255;
-
-    const position = new THREE.Vector3((
-        x - 0.5) * widthSegments,
-        y * amplitude,
-        (z - 0.5) * heightSegments);
-
-    return position;
-}
-
-function createInstancedTrees(count) {
-    console.log('Generating `' + count + '` instances of tree');
-
-    let logHeight = 4.0;
-    const treeLogGeometry   = new THREE.CylinderGeometry(
-        0.30, 0.30, logHeight, 40, 40);
-    treeLogGeometry.translate(0, logHeight/2.0, 0);
-    const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry();
-    instancedTreeLogGeometry.copy(treeLogGeometry);
-    const treeLogMaterial   = new THREE.MeshPhongMaterial({color: 0x7c3f00});
-    const instancedTreeLogs = new THREE.InstancedMesh(
-        instancedTreeLogGeometry,
-        treeLogMaterial,
-        count);
-
-    const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
-    const instancedTreeLeavesGeometry = new THREE.InstancedBufferGeometry();
-    instancedTreeLeavesGeometry.copy(treeLeavesGeometry);
-    const treeLeavesMaterial  = new THREE.MeshPhongMaterial({color: 0x365829});
-    const instancedTreeLeaves = new THREE.InstancedMesh(
-        instancedTreeLeavesGeometry,
-        treeLeavesMaterial,
-        count);
-
-    const rotMatrix         = new THREE.Matrix4();
-    const translationMatrix = new THREE.Matrix4();
-    const treeLogMatrix     = new THREE.Matrix4();
-    const treeLeavesMatrix  = new THREE.Matrix4();
-
-    for (let i = 0; i < count; i++) {
-        let position = getRandomPositionInTerrain();
-        let j = 0;
-        while((position.y > 4.0) || (position.y < 2.5)) {
-            position = getRandomPositionInTerrain();
-            // console.log(position);
-            if(j++ == 100) {
-                break;
-            }
-        }
-
-        position.y += amplitudeBottom;
-        translationMatrix.makeTranslation(position);
-        treeLogMatrix.identity();
-        treeLeavesMatrix.identity();
-
-        let scale = 0.5 + (Math.random()*(logHeight/3));
-        treeLogMatrix.makeScale(1, scale, 1);
-        treeLogMatrix.premultiply(translationMatrix);
-
-        position.y += scale * logHeight;
-        translationMatrix.makeTranslation(position);
-        treeLeavesMatrix.premultiply(translationMatrix);
-
-        instancedTreeLogs.setMatrixAt(i, treeLogMatrix);
-        instancedTreeLeaves.setMatrixAt(i, treeLeavesMatrix);
-    }
-
-    return [instancedTreeLogs, instancedTreeLeaves];
-}
-
 // La funcion devuelve una geometria de Three.js
 // width: Ancho del plano
 // height: Alto del plano
@@ -159,7 +22,7 @@ function createInstancedTrees(count) {
 // widthSegments: Numero de segmentos en el ancho
 // heightSegments: Numero de segmentos en el alto
 // texture: Textura que se usara para la elevacion
-function elevationGeometry(width, height, amplitude, widthSegments, heightSegments, texture) {
+export function elevationGeometry(width, height, amplitude, widthSegments, heightSegments, texture) {
     console.log('Generating terrain geometry');
     let geometry = new THREE.BufferGeometry();
 
@@ -272,54 +135,6 @@ function elevationGeometry(width, height, amplitude, widthSegments, heightSegmen
     return geometry;
 }
 
-function buildScene() {
-    console.log('Building scene');
-
-    const width = 100;
-    const height = 100;
-
-    terrainGeometry = elevationGeometry(
-        width, height,
-        amplitude,
-        widthSegments, heightSegments,
-        textures.elevationMap.object);
-
-    console.log('Applying textures');
-    terrainMaterial = new THREE.RawShaderMaterial({
-        uniforms: {
-            dirtSampler: { type: 't', value: textures.tierra.object },
-            rockSampler: { type: 't', value: textures.roca.object },
-            grassSampler: { type: 't', value: textures.pasto.object },
-            scale: { type: 'f', value: 3.0 },
-            terrainAmplitude: { type: 'f', value: amplitude },
-            terrainAmplitudeBottom: { type: 'f', value: amplitudeBottom },
-            worldNormalMatrix: { type: 'm4', value: null },
-            dirtStepWidth: { type: 'f', value: 0.20 },
-            rockStepWidth: { type: 'f', value: 0.15 },
-        },
-        vertexShader: vertexShader,
-        fragmentShader: fragmentShader,
-        side: THREE.DoubleSide,
-    });
-    terrainMaterial.needsUpdate = true;
-
-    terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
-    terrain.position.set(0, amplitudeBottom, 0);
-    scene.add(terrain);
-
-    console.log('Generating water');
-    const waterGeometry = new THREE.PlaneGeometry(width/2, height);
-    const waterMaterial = new THREE.MeshPhongMaterial( {color: 0x12ABFF, side: THREE.DoubleSide} );
-    const water = new THREE.Mesh( waterGeometry, waterMaterial );
-    water.rotateX(Math.PI/2);
-    water.position.set(0, 0.75, 0);
-    scene.add(water);
-
-    const [treeLogs, treeLeaves] = createInstancedTrees(100);
-    scene.add(treeLogs);
-    scene.add(treeLeaves);
-}
-
 function onTextureLoaded(key, texture) {
     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
     textures[key].object = texture;
@@ -349,23 +164,7 @@ function loadTextures(callback) {
     }
 }
 
-function createMenu() {
-    const gui = new dat.GUI({ width: 400 });
-    gui.add(terrainMaterial.uniforms.scale, 'value', 1.00, 5.00).name('Terrain texture scale');
-    gui.add(terrainMaterial.uniforms.dirtStepWidth, 'value', 0.0, 1.0).name('dirt step width');
-    gui.add(terrainMaterial.uniforms.rockStepWidth, 'value', 0.10, 0.50).name('rock step width');
-}
-
-function mainLoop() {
-    requestAnimationFrame(mainLoop);
-    renderer.render(scene, camera);
+function main() {
 }
 
-setupThreeJs();
 loadTextures(main);
-
-function main() {
-    buildScene();
-    createMenu();
-    mainLoop();
-}
diff --git a/tp/src/track-map.js b/tp/src/track-map.js
@@ -1,76 +1,19 @@
 import * as THREE from 'three';
-import * as dat from 'dat.gui';
-import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
-import { vertexShader, fragmentShader } from '/assets/shaders.js';
 
-let scene, camera, renderer, container, terrainMaterial, terrainGeometry, terrain;
 let treesForbiddenMapData, treesForbiddenMap, elevationMap, elevationMapData;
 
-const widthSegments   = 100;
-const heightSegments  = 100;
-const amplitude       = 8;
-const amplitudeBottom = -1.00;
-
 const textures = {
-    tierra:           { url: '/assets/tierra.jpg', object: null },
-    roca:             { url: '/assets/roca.jpg', object: null },
-    pasto:            { url: '/assets/pasto.jpg', object: null },
     elevationMap:     { url: '/assets/elevation_map2.png', object: null },
     treeForbiddenMap: { url: '/assets/tree_forbidden_zone_map.png', object: null }
 };
 
-function onResize() {
-    camera.aspect = container.offsetWidth / container.offsetHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-}
-
-function setupThreeJs() {
-    scene = new THREE.Scene();
-    container = document.getElementById('mainContainer');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setClearColor(0x606060);
-    container.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(
-        35, window.innerWidth/window.innerHeight, 0.1, 1000);
-    camera.position.set(100, 120, -100);
-    camera.lookAt(0, 0, 0);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
-    //scene.add(hemisphereLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
-    directionalLight.position.set(100, 100, 100);
-    scene.add(directionalLight);
-
-    const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
-    // scene.add(directionalLightHelper);
-
-     const gridHelper = new THREE.GridHelper(150, 150);
-     scene.add(gridHelper);
-
-    const axesHelper = new THREE.AxesHelper( 5 );
-    scene.add( axesHelper );
-
-    window.addEventListener('resize', onResize);
-    onResize();
-}
-
+const widthSegments   = 100;
+const heightSegments  = 100;
+const amplitude       = 8;
+const amplitudeBottom = -1.00;
 const imgWidth  = 512;
 const imgHeight = 512;
 
-// (x, y) ∈ [imgHeight, imgWidth] -> son un punto de la imagen 
-function getPixelIndex(x, y) {
-    return Math.floor(x + y*imgWidth*4);
-}
-
 function getPixel(imgData, index) {
   let i = index*4, d = imgData.data
   return [d[i],d[i+1],d[i+2],d[i+3]] // Returns array [R,G,B,A]
@@ -125,76 +68,6 @@ function getRandomPositionInTerrain(padding = 0) {
     return position;
 }
 
-function createInstancedTrees(count) {
-    console.log('Generating `' + count + '` instances of tree');
-
-    let logHeight = 4.0;
-    const treeLogGeometry   = new THREE.CylinderGeometry(
-        0.30, 0.30, logHeight, 40, 40);
-    treeLogGeometry.translate(0, logHeight/2.0, 0);
-    const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry();
-    instancedTreeLogGeometry.copy(treeLogGeometry);
-    const treeLogMaterial   = new THREE.MeshPhongMaterial({color: 0x7c3f00});
-    const instancedTreeLogs = new THREE.InstancedMesh(
-        instancedTreeLogGeometry,
-        treeLogMaterial,
-        count);
-
-    const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
-    const instancedTreeLeavesGeometry = new THREE.InstancedBufferGeometry();
-    instancedTreeLeavesGeometry.copy(treeLeavesGeometry);
-    const treeLeavesMaterial  = new THREE.MeshPhongMaterial({color: 0x365829});
-    const instancedTreeLeaves = new THREE.InstancedMesh(
-        instancedTreeLeavesGeometry,
-        treeLeavesMaterial,
-        count);
-
-    const rotMatrix         = new THREE.Matrix4();
-    const translationMatrix = new THREE.Matrix4();
-    const treeLogMatrix     = new THREE.Matrix4();
-    const treeLeavesMatrix  = new THREE.Matrix4();
-
-    const treesBorderPadding = 3.0;
-    for (let i = 0; i < count; i++) {
-        let position = getRandomPositionInTerrain(treesBorderPadding);
-        for(let j = 0; isForbbidenPosition(position); ++j) {
-            position = getRandomPositionInTerrain(treesBorderPadding);
-            if(j++ == 1000) { // maximo de iteraciones
-                break;
-            }
-        }
-
-        if(isForbbidenPosition(position)) {
-            continue;
-        }
-
-        const treeOffset = 0.25;
-        // 1.50 numbero magico para posicionar correctamente los arboles con
-        // respecto al terreno
-        position.x -= (widthSegments+treesBorderPadding+1.50)/2;
-        position.y += amplitudeBottom - treeOffset;
-        position.z -= (heightSegments+treesBorderPadding)/2;
-        translationMatrix.makeTranslation(position);
-        treeLogMatrix.identity();
-        treeLeavesMatrix.identity();
-
-        let scale = 0.6 + (Math.random()*(logHeight/3));
-        treeLogMatrix.makeScale(1, scale, 1);
-        treeLogMatrix.premultiply(translationMatrix);
-
-        position.y += scale*logHeight;
-        translationMatrix.makeTranslation(position);
-        treeLeavesMatrix.premultiply(translationMatrix);
-
-        instancedTreeLogs.setMatrixAt(i, treeLogMatrix);
-        instancedTreeLeaves.setMatrixAt(i, treeLeavesMatrix);
-    }
-
-    console.log('Done generating `' + count + '` instances of tree');
-    return [instancedTreeLogs, instancedTreeLeaves];
-}
-
-
 // La funcion devuelve una geometria de Three.js
 // width: Ancho del plano
 // height: Alto del plano
@@ -297,7 +170,79 @@ function elevationGeometry(width, height, amplitude, widthSegments, heightSegmen
     return geometry;
 }
 
-function buildScene() {
+// devuelve un arreglo de 2 `instancedMesh` con los troncos y copas de los arboles
+export function createInstancedTrees(count) {
+    console.log('Generating `' + count + '` instances of tree');
+
+    let logHeight = 4.0;
+    const treeLogGeometry   = new THREE.CylinderGeometry(
+        0.30, 0.30, logHeight, 40, 40);
+    treeLogGeometry.translate(0, logHeight/2.0, 0);
+    const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry();
+    instancedTreeLogGeometry.copy(treeLogGeometry);
+    const treeLogMaterial   = new THREE.MeshPhongMaterial({color: 0x7c3f00});
+    const instancedTreeLogs = new THREE.InstancedMesh(
+        instancedTreeLogGeometry,
+        treeLogMaterial,
+        count);
+
+    const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
+    const instancedTreeLeavesGeometry = new THREE.InstancedBufferGeometry();
+    instancedTreeLeavesGeometry.copy(treeLeavesGeometry);
+    const treeLeavesMaterial  = new THREE.MeshPhongMaterial({color: 0x365829});
+    const instancedTreeLeaves = new THREE.InstancedMesh(
+        instancedTreeLeavesGeometry,
+        treeLeavesMaterial,
+        count);
+
+    const rotMatrix         = new THREE.Matrix4();
+    const translationMatrix = new THREE.Matrix4();
+    const treeLogMatrix     = new THREE.Matrix4();
+    const treeLeavesMatrix  = new THREE.Matrix4();
+
+    const treesBorderPadding = 3.0;
+    for (let i = 0; i < count; i++) {
+        let position = getRandomPositionInTerrain(treesBorderPadding);
+        for(let j = 0; isForbbidenPosition(position); ++j) {
+            position = getRandomPositionInTerrain(treesBorderPadding);
+            if(j++ == 1000) { // maximo de iteraciones
+                break;
+            }
+        }
+
+        if(isForbbidenPosition(position)) {
+            continue;
+        }
+
+        const treeOffset = 0.25;
+        // 1.50 numbero magico para posicionar correctamente los arboles con
+        // respecto al terreno
+        position.x -= (widthSegments+treesBorderPadding+1.50)/2;
+        position.y += amplitudeBottom - treeOffset;
+        position.z -= (heightSegments+treesBorderPadding)/2;
+        translationMatrix.makeTranslation(position);
+        treeLogMatrix.identity();
+        treeLeavesMatrix.identity();
+
+        let scale = 0.6 + (Math.random()*(logHeight/3));
+        treeLogMatrix.makeScale(1, scale, 1);
+        treeLogMatrix.premultiply(translationMatrix);
+
+        position.y += scale*logHeight;
+        translationMatrix.makeTranslation(position);
+        treeLeavesMatrix.premultiply(translationMatrix);
+
+        instancedTreeLogs.setMatrixAt(i, treeLogMatrix);
+        instancedTreeLeaves.setMatrixAt(i, treeLeavesMatrix);
+    }
+
+    console.log('Done generating `' + count + '` instances of tree');
+    return [instancedTreeLogs, instancedTreeLeaves];
+}
+
+
+
+function buildTrees() {
     console.log('Building scene');
 
     const width  = widthSegments;
@@ -336,11 +281,40 @@ function buildScene() {
     const water = new THREE.Mesh( waterGeometry, waterMaterial );
     water.rotateX(Math.PI/2);
     water.position.set(0, 0.75, -1.00);
-    scene.add(water);
+    // scene.add(water);
 
     const [treeLogs, treeLeaves] = createInstancedTrees(250);
-    scene.add(treeLogs);
-    scene.add(treeLeaves);
+    return 
+    // scene.add(treeLogs);
+    // scene.add(treeLeaves);
+}
+
+function loadMapsData() {
+    console.log("Loading maps data");
+
+    // Creamos un canvas para poder leer los valores de los píxeles de la textura
+    let canvas = document.createElement('canvas');
+    let ctx = canvas.getContext('2d');
+
+    let treesForbiddenMapImage = textures.treeForbiddenMap.object.image;
+    let elevationMapImage = textures.elevationMap.object.image;
+
+    // ambos mapas deben tener el mismo tamaño
+    const imgWidth  = widthSegments;
+    const imgHeight = heightSegments;
+
+    canvas.width  = imgWidth;
+    canvas.height = imgHeight;
+
+    ctx.drawImage(treesForbiddenMapImage, 0, 0, imgWidth, imgHeight);
+    treesForbiddenMap = ctx.getImageData(0, 0, imgWidth, imgHeight);
+    treesForbiddenMapData = treesForbiddenMap.data;
+
+    ctx.drawImage(elevationMapImage, 0, 0, imgWidth, imgHeight);
+    elevationMap = ctx.getImageData(0, 0, imgWidth, imgHeight);
+    elevationMapData = elevationMap.data
+
+    console.log("All maps data loaded succesfully");
 }
 
 function onTextureLoaded(key, texture) {
@@ -372,52 +346,10 @@ function loadTextures(callback) {
     }
 }
 
-function createMenu() {
-    const gui = new dat.GUI({ width: 400 });
-    gui.add(terrainMaterial.uniforms.scale, 'value', 1.00, 5.00).name('Terrain texture scale');
-    gui.add(terrainMaterial.uniforms.dirtStepWidth, 'value', 0.0, 1.0).name('dirt step width');
-    gui.add(terrainMaterial.uniforms.rockStepWidth, 'value', 0.10, 0.50).name('rock step width');
-}
-
-function mainLoop() {
-    requestAnimationFrame(mainLoop);
-    renderer.render(scene, camera);
-}
-
-function loadMapsData() {
-    console.log("Loading maps data");
-
-    // Creamos un canvas para poder leer los valores de los píxeles de la textura
-    let canvas = document.createElement('canvas');
-    let ctx = canvas.getContext('2d');
-
-    let treesForbiddenMapImage = textures.treeForbiddenMap.object.image;
-    let elevationMapImage = textures.elevationMap.object.image;
-
-    // ambos mapas deben tener el mismo tamaño
-    const imgWidth  = widthSegments;
-    const imgHeight = heightSegments;
-
-    canvas.width  = imgWidth;
-    canvas.height = imgHeight;
-
-    ctx.drawImage(treesForbiddenMapImage, 0, 0, imgWidth, imgHeight);
-    treesForbiddenMap = ctx.getImageData(0, 0, imgWidth, imgHeight);
-    treesForbiddenMapData = treesForbiddenMap.data;
-
-    ctx.drawImage(elevationMapImage, 0, 0, imgWidth, imgHeight);
-    elevationMap = ctx.getImageData(0, 0, imgWidth, imgHeight);
-    elevationMapData = elevationMap.data
-
-    console.log("All maps data loaded succesfully");
-}
 
 function main() {
     loadMapsData();
-    buildScene();
-    // getTrainTrackShape();
-    mainLoop();
+    // buildScene();
 }
 
-setupThreeJs();
 loadTextures(main);
diff --git a/tp/src/train.js b/tp/src/train.js
@@ -1,56 +1,6 @@
 import * as THREE from 'three';
-import * as dat from 'dat.gui';
 import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
 
-import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
-
-let scene, camera, renderer, container, terrainMaterial, instancedTrees;
-
-let time = 0.0;
-
-function onResize() {
-    camera.aspect = container.offsetWidth / container.offsetHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-}
-
-function setupThreeJs() {
-    scene = new THREE.Scene();
-    container = document.getElementById('mainContainer');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setClearColor(0x606060);
-    container.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 1000);
-    camera.position.set(-50, 60, 50);
-    camera.lookAt(0, 0, 0);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-
-    const ambientLight = new THREE.AmbientLight(0xAAAAAA);
-    scene.add(ambientLight);
-
-    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
-    scene.add(hemisphereLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
-    directionalLight.position.set(100, 100, 100);
-    scene.add(directionalLight);
-
-    const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
-    // scene.add(directionalLightHelper);
-
-    const gridHelper = new THREE.GridHelper(50, 20);
-    scene.add(gridHelper);
-
-    const axesHelper = new THREE.AxesHelper(5);
-    scene.add(axesHelper);
-
-    window.addEventListener('resize', onResize);
-    onResize();
-}
-
 const steamChamberLen = 20;
 const steamChamberRad = 5;
 const steamChamberEndRad = steamChamberRad+0.75;
@@ -186,9 +136,9 @@ function buildTrainChassis() {
     return chassis;
 }
 
-function buildTrain() {
+export function buildTrain() {
     console.log('Building train');
-    const train = new THREE.Group();
+    const train = new THREE.Object3D();
 
     const chassisGeometry = buildTrainChassis();
     const chassisMaterial = new THREE.MeshPhongMaterial({
@@ -240,7 +190,6 @@ function buildTrain() {
     a2.position.set(0, wheelOffset, wheelRad*2.5);
     a3.position.set(0, wheelOffset, -wheelRad*2.5);
 
-
     const cylinderLeft = new THREE.CylinderGeometry(2.25, 2.5, steamCylindersLen);
     cylinderLeft.rotateX(Math.PI/2);
     cylinderLeft.translate(steamChamberRad-0.25, 0, steamChamberLen-steamCylindersLen/1.5);
@@ -294,30 +243,14 @@ function buildTrain() {
     chassis.add(crankLeft);
     chassis.add(crankRight);
 
-    chassis.translateY(-wheelOffset);
+    // chassis.translateY(-wheelOffset);
+    updateTrainCrankPosition();
 
-    train.position.set(0, 2, 0);
+    train.position.set(0, 1.9, 0);
     return train;
 }
 
-function buildScene() {
-    console.log('Building scene');
-
-    const train = buildTrain();
-    scene.add(train);
-}
-
-function onTextureLoaded(key, texture) {
-    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
-    textures[key].object = texture;
-    console.log('Texture `' + key + '` loaded');
-}
-
-function mainLoop() {
-    time += 0.05;
-
-    requestAnimationFrame(mainLoop);
-
+export function updateTrainCrankPosition(time = 0.0) {
     crankLeft.position.set(-steamChamberRad-crankWidth/2,
         wheelOffset+1.00*(Math.sin(time*Math.PI/2)),
         crankOffset - 1.00*(Math.cos(time*Math.PI/2)));
@@ -325,14 +258,4 @@ function mainLoop() {
     crankRight.position.set(steamChamberRad+crankWidth/2,
         wheelOffset+1.00*(Math.sin(time*Math.PI/2)),
         crankOffset - 1.00*(Math.cos(time*Math.PI/2)));
-
-    renderer.render(scene, camera);
 }
-
-function main() {
-    buildScene();
-    mainLoop();
-}
-
-setupThreeJs();
-main();
diff --git a/tp/src/tunnel.js b/tp/src/tunnel.js
@@ -1,91 +1,8 @@
 import * as THREE from 'three';
-import * as dat from 'dat.gui';
-import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 
-let scene, camera, renderer, container;
-
-const textures = {
-    madera: { url: '/assets/madera.jpg', object: null },
-};
-
-function onResize() {
-    camera.aspect = container.offsetWidth / container.offsetHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-}
-
-function setupThreeJs() {
-    scene = new THREE.Scene();
-    container = document.getElementById('mainContainer');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setClearColor(0x606060);
-    container.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 1000);
-    camera.position.set(-50, 60, 50);
-    camera.lookAt(0, 0, 0);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
-    scene.add(hemisphereLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
-    directionalLight.position.set(100, 100, 100);
-    scene.add(directionalLight);
-
-    const gridHelper = new THREE.GridHelper(50, 20);
-    scene.add(gridHelper);
-
-    const axesHelper = new THREE.AxesHelper(5);
-    scene.add(axesHelper);
-
-    window.addEventListener('resize', onResize);
-    onResize();
-}
-
-function buildScene() {
-    console.log('Building scene');
-}
-
-function onTextureLoaded(key, texture) {
-    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
-    textures[key].object = texture;
-    console.log('Texture `' + key + '` loaded');
-}
-
-function loadTextures(callback) {
-    const loadingManager = new THREE.LoadingManager();
-
-    loadingManager.onLoad = () => {
-        console.log('All textures loaded');
-        callback();
-    };
-
-    for (const key in textures) {
-        console.log("Loading textures");
-        const loader = new THREE.TextureLoader(loadingManager);
-        const texture = textures[key];
-        texture.object = loader.load(
-            texture.url,
-            onTextureLoaded.bind(this, key),
-            null,
-            (error) => {
-                console.error(error);
-            }
-        );
-    }
-}
-
-function generateTunnel() {
-    const tunnelHeight        = 20;
-    const tunnelWidth         = 14;
-    const tunnelWallThickness = 0.5;
-    const tunnelLen           = 26;
+export function generateTunnelGeometry(
+    tunnelHeight = 20, tunnelWidth = 14,
+    tunnelWallThickness = 0.5, tunnelLen = 26) {
 
     const path = new THREE.Path();
     path.moveTo(-tunnelWidth/2, 0);
@@ -115,15 +32,6 @@ function generateTunnel() {
     path.moveTo(-tunnelWidth/2, 0);
 
     const points = path.getPoints();
-
-    /*
-    // muestra la curva utilizada para la extrusión
-    const geometry = new THREE.BufferGeometry().setFromPoints(points);
-    const lineMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 });
-    const curveObject = new THREE.Line(geometry, lineMaterial);
-    scene.add(curveObject);
-    */
-
     const shape = new THREE.Shape(points);
 
     const extrudeSettings = {
@@ -134,33 +42,5 @@ function generateTunnel() {
 
     const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
     geometry.translate(0, 0, -tunnelLen/2);
-
-    textures.madera.object.wrapS = THREE.RepeatWrapping;
-    textures.madera.object.wrapT = THREE.RepeatWrapping;
-    textures.madera.object.repeat.set(0.10, 0.10);
-    textures.madera.object.anisotropy = 16;
-
-    const tunnelMaterial = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        transparent: false,
-        opacity: 1.0,
-        shininess: 10,
-        map: textures.madera.object
-    });
-
-    const mesh = new THREE.Mesh(geometry, tunnelMaterial) ;
-    scene.add(mesh);
-}
-
-function mainLoop() {
-    requestAnimationFrame(mainLoop);
-    renderer.render(scene, camera);
-}
-
-function main() {
-    generateTunnel();
-    mainLoop();
+    return geometry;
 }
-
-setupThreeJs();
-loadTextures(main);