TA159

Notas, resueltos y trabajos practicos de la materia Sistemas Gráficos
Index Commits Files Refs Submodules README LICENSE
tp/src/scene.js (30940B)
   1 import * as THREE from 'three';
   2 import * as dat from 'dat.gui';
   3 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
   4 import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
   5 import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
   6 import Stats from 'three/examples/jsm/libs/stats.module.js';
   7 import { updateTrainCrankPosition } from '/src/train.js';
   8 import { generateTunnelGeometry } from '/src/tunnel.js';
   9 import { createInstancedTrees } from '/src/trees.js';
  10 import { elevationGeometry } from '/src/terrain.js';
  11 import { generateBridge } from '/src/bridge.js';
  12 import { buildTrain } from '/src/train.js';
  13 import {
  14     getRailsPathPosAt,
  15     buildRailsGeometry,
  16     buildRailsFoundationGeometry
  17 } from '/src/rails.js';
  18 
  19 let scene, camera, renderer, time, prevTime, gui, stats;
  20 let terrainGeometry, terrain, treesForbiddenMapData, treesForbiddenMap, elevationMap, elevationMapData;
  21 let firstPersonControls, orbitControls;
  22 let train, trainLight, trainLight2, trainLight3;
  23 
  24 let helpers = [];
  25 let cameras = [];
  26 let camerasName = [];
  27 let objects = [];
  28 let lights = {
  29     ambient:     { object: null },
  30     directional: { object: null },
  31     hemisphere:  { object: null }
  32 };
  33 
  34 let settings = {
  35     animationEnable: false,
  36     showTrain: true,
  37     currCameraIndex: 0,
  38     nightMode: true,
  39     showHelpers: false,
  40     showFps: true,
  41     currCameraName: "",
  42     shadows: false
  43 };
  44 
  45 let raycaster;
  46 
  47 let moveForward = false;
  48 let moveForwardRunning = false;
  49 let moveBackward = false;
  50 let moveLeft = false;
  51 let moveRight = false;
  52 
  53 const velocity = new THREE.Vector3();
  54 const direction = new THREE.Vector3();
  55 
  56 // actualizar la variable global `amplitude` de '/src/track-map/'
  57 const widthSegments   = 150;
  58 const heightSegments  = 150;
  59 const amplitude       = 10;
  60 const amplitudeBottom = -2.10; // terrain offset
  61 
  62 import skyDayUrl           from './assets/sky_day_void.jpg'
  63 import skyNightUrl         from './assets/sky_night.jpg'
  64 import rocaUrl             from './assets/roca.jpg'
  65 import pastoUrl            from './assets/pasto.jpg'
  66 import tierraUrl           from './assets/tierra.jpg'
  67 import maderaUrl           from './assets/madera.jpg'
  68 import durmientesUrl       from './assets/durmientes.jpg'
  69 import elevationMapUrl     from './assets/elevation_map_wider_river.png'
  70 import treeForbiddenMapUrl from './assets/tree_forbidden_zone_map_wider_path.png'
  71 
  72 const textures = {
  73     skyDay:           { url: skyDayUrl, object: null },
  74     skyNight:         { url: skyNightUrl, object: null },
  75     roca:             { url: rocaUrl, object: null },
  76     pasto:            { url: pastoUrl, object: null },
  77     tierra:           { url: tierraUrl, object: null },
  78     madera:           { url: maderaUrl, object: null },
  79     durmientes:       { url: durmientesUrl, object: null },
  80     elevationMap:     { url: elevationMapUrl, object: null },
  81     treeForbiddenMap: { url: treeForbiddenMapUrl, object: null }
  82 };
  83 
  84 function onResize() {
  85     // const aspect = container.offsetWidth / container.offsetHeight;
  86     const aspect = window.innerWidth / window.innerHeight;
  87 
  88     for(let i = 0; i < cameras.length; ++i) {
  89         if(cameras[i] != undefined) {
  90             cameras[i].aspect = aspect;
  91             cameras[i].updateProjectionMatrix();
  92         }
  93     }
  94 
  95     renderer.setSize( window.innerWidth, window.innerHeight );
  96 }
  97 
  98 function updateCamera() {
  99     orbitControls.enabled = false;
 100     blocker.style.display = 'none';
 101     instructions.style.display = 'none';
 102     if(settings.nightMode == true) {
 103         trainLight.intensity = 200;
 104         trainLight.distance = 100;
 105     }
 106 
 107     firstPersonControls.unlock();
 108 
 109     let currCamera = cameras[settings.currCameraIndex];
 110     switch(currCamera.name) {
 111         case "topView":
 112             orbitControls.enabled = true;
 113             break;
 114         case "firstPersonCamera":
 115             blocker.style.display = 'block';
 116             instructions.style.display = 'flex';
 117             break;
 118         case "trainCamera":
 119         case "trainConductorCamera":
 120             // por alguna razon cuando la camara es `trainConductorCamera`
 121             // o `trainCamera` la luz principal del tren se ve mas tenue
 122             if(settings.nightMode == true) {
 123                 trainLight.intensity = 1000;
 124                 trainLight.distance = 1000;
 125             }
 126             break;
 127         default:
 128             break;
 129     }
 130     onResize();
 131     settings.currCameraName = camerasName[settings.currCameraIndex];
 132 }
 133 
 134 function prevCamera() {
 135     const camerasCount = cameras.length;
 136 
 137     if(settings.currCameraIndex == 0) {
 138         settings.currCameraIndex = (camerasCount - 1);
 139     } else {
 140         settings.currCameraIndex -= 1;
 141     }
 142     updateCamera();
 143 }
 144 
 145 function nextCamera() {
 146     const camerasCount = cameras.length;
 147 
 148     if(settings.currCameraIndex == (camerasCount - 1)) {
 149         settings.currCameraIndex = 0;
 150     } else {
 151         settings.currCameraIndex += 1;
 152     }
 153 
 154     updateCamera();
 155 }
 156 
 157 const blocker = document.getElementById( 'blocker' );
 158 const instructions = document.getElementById( 'instructions' );
 159 
 160 function firstPersonCameraHandler(eventName) {
 161     // if(cameras[settings.currCameraIndex].name != "firstPersonCamera") {
 162     //     console.log(cameras[settings.currCameraIndex].name);
 163     //     return;
 164     // }
 165 
 166     switch(eventName) {
 167         case 'click':
 168             console.log('click');
 169             firstPersonControls.lock();
 170             break;
 171         case 'lock':
 172             console.log('lock');
 173             instructions.style.display = 'none';
 174             blocker.style.display = 'none';
 175             break;
 176         case 'unlock':
 177             console.log('unlock');
 178             blocker.style.display = 'block';
 179             instructions.style.display = 'flex';
 180             break;
 181     }
 182 }
 183 
 184 function keyHandler(event) {
 185     if(event.type == 'keydown') {
 186         switch (event.code) {
 187             case 'ArrowUp':
 188             case 'KeyW':
 189                 moveForward = true;
 190                 if(event.shiftKey) {
 191                     moveForwardRunning = true;
 192                 }
 193                 break;
 194             case 'ArrowLeft':
 195             case 'KeyA':
 196                 moveLeft = true;
 197                 break;
 198             case 'ArrowDown':
 199             case 'KeyS':
 200                 moveBackward = true;
 201                 break;
 202             case 'ArrowRight':
 203             case 'KeyD':
 204                 moveRight = true;
 205                 break;
 206             case "KeyC":
 207                 if(event.shiftKey) {
 208                     prevCamera();
 209                 } else {
 210                     nextCamera();
 211                 }
 212                 if(gui != undefined) {
 213                     gui.__controllers[6].updateDisplay();
 214                 }
 215                 break;
 216             case 'Space':
 217                 // if (firstPersonControls.isLocked === true) {
 218                 //     console.log(canJump);
 219                 //     velocity.y += 350;
 220                 //     break;
 221                 // }
 222                 settings.animationEnable = !settings.animationEnable;
 223                 if(gui != undefined) {
 224                     // update gui 'Animations' checkbox
 225                     gui.__controllers[0].updateDisplay();
 226                 }
 227                 break;
 228         }
 229         switch(event.key) {
 230             case "Shift":
 231                 if(!moveForwardRunning) {
 232                     moveForwardRunning = true;
 233                 }
 234                 break;
 235             default:
 236                 break;
 237         }                
 238     } else {
 239         // key up
 240         switch (event.code) {
 241             case 'ArrowUp':
 242             case 'KeyW':
 243                 moveForward = false;
 244                 moveForwardRunning = false;
 245                 break;
 246             case 'ArrowLeft':
 247             case 'KeyA':
 248                 moveLeft = false;
 249                 break;
 250             case 'ArrowDown':
 251             case 'KeyS':
 252                 moveBackward = false;
 253                 break;
 254             case 'ArrowRight':
 255             case 'KeyD':
 256                 moveRight = false;
 257                 break;
 258         }
 259         switch(event.key) {
 260             case "Shift":
 261                 if(moveForwardRunning) {
 262                     moveForwardRunning = false;
 263                 }
 264                 break;
 265             default:
 266                 break;
 267         }
 268     }
 269 }
 270 
 271 function setupFirstPersonControls() {
 272     const firstPersonCamera = new THREE.PerspectiveCamera(
 273         50, window.innerWidth / window.innerHeight, 0.1, 1000);
 274 
 275     firstPersonCamera.position.set(0, 5, 20);
 276     firstPersonCamera.lookAt(-10, 5, 0);
 277     firstPersonCamera.name = "firstPersonCamera"
 278     cameras.push(firstPersonCamera);
 279     camerasName.push("Primera Persona");
 280 
 281     firstPersonControls = new PointerLockControls(firstPersonCamera, document.body);
 282 
 283 
 284     instructions.addEventListener('click', function() {
 285         // console.log(event);
 286         firstPersonCameraHandler('click');
 287     });
 288 
 289     firstPersonControls.addEventListener('lock', function() {
 290         // console.log(event);
 291         firstPersonCameraHandler('lock');
 292     });
 293 
 294     firstPersonControls.addEventListener('unlock', function() {
 295         // console.log(event);
 296         firstPersonCameraHandler('unlock');
 297     });
 298     scene.add(firstPersonControls.getObject());
 299 
 300     window.addEventListener('keydown', (event) => {
 301         keyHandler(event);
 302     });
 303 
 304     window.addEventListener('keyup', (event) => {
 305         keyHandler(event);
 306     });
 307 }
 308 
 309 function setupThreeJs() {
 310     scene = new THREE.Scene();
 311     renderer = new THREE.WebGLRenderer();
 312     renderer.setPixelRatio( window.devicePixelRatio );
 313     renderer.setSize( window.innerWidth, window.innerHeight );
 314     renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
 315     renderer.shadowMap.enabled = settings.shadows;
 316 
 317     document.body.appendChild(renderer.domElement);
 318 
 319     stats = new Stats();
 320     if(settings.showFps == true) {
 321         document.body.appendChild(stats.dom);
 322     }
 323 
 324     const topView = new THREE.PerspectiveCamera(
 325         35, window.innerWidth / window.innerHeight, 0.1, 1000);
 326 
 327     topView.position.set(-32, 38, 70);
 328     topView.lookAt(0, 0, 0);
 329     topView.name = "topView"
 330     cameras.push(topView);
 331     camerasName.push("Vista Global");
 332 
 333     orbitControls = new OrbitControls(topView, renderer.domElement);
 334 
 335     lights.ambient.object = new THREE.AmbientLight(0xffffff);
 336 
 337     lights.hemisphere.object = new THREE.HemisphereLight(0xFFFFFF, 0x000000, 0.25);
 338 
 339     lights.directional.object = new THREE.DirectionalLight(0xffffff, 1);
 340     lights.directional.object.position.set(-35, 35, 35);
 341 
 342     // Set up shadow properties for the light
 343     lights.directional.object.castShadow            = true;
 344     lights.directional.object.shadow.mapSize.width  = 512;
 345     lights.directional.object.shadow.mapSize.height = 512;
 346 
 347     lights.directional.object.shadow.camera = new THREE.OrthographicCamera(
 348         -65, 65, 45, -35, 1.0, 112);
 349 
 350     const directionalLightShadowsHelper = new THREE.CameraHelper(lights.directional.object.shadow.camera);
 351     directionalLightShadowsHelper.visible = settings.showHelpers;
 352     scene.add(directionalLightShadowsHelper);
 353     helpers.push(directionalLightShadowsHelper);
 354 
 355     scene.add(lights.ambient.object);
 356     scene.add(lights.hemisphere.object);
 357     scene.add(lights.directional.object);
 358 
 359     if(settings.nightMode == true) {
 360         lights.ambient.object.visible = false;
 361         lights.hemisphere.object.intensity = 0;
 362         lights.directional.object.color.setHex(0xcdddfe); // 0x090254; 0xa8a1fd
 363         scene.background = textures.skyNight.object;
 364         lights.directional.object.position.set(35, 35, 35); // match the skybox texture moon light position
 365     } else {
 366         lights.ambient.object.visible = true;
 367         lights.hemisphere.object.intensity = 1;
 368         lights.directional.object.intensity = 1;
 369         lights.directional.object.color.setHex(0xFFFFFF);
 370         scene.background = textures.skyDay.object;
 371         lights.directional.object.position.set(-35, 35, 35);
 372     }
 373     
 374     const hemisphereLightHelper = new THREE.HemisphereLightHelper(lights.hemisphere.object, 5);
 375     helpers.push(hemisphereLightHelper);
 376 
 377     const directionalLightHelper = new THREE.DirectionalLightHelper(lights.directional.object, 5);
 378     helpers.push(directionalLightHelper);
 379 
 380     const gridHelper = new THREE.GridHelper(100, 100);
 381     helpers.push(gridHelper);
 382 
 383     const axesHelper = new THREE.AxesHelper(5);
 384     helpers.push(axesHelper);
 385 
 386     for(let i = 0; i < helpers.length; ++i) {
 387         helpers[i].visible = settings.showHelpers;
 388         scene.add(helpers[i]);
 389     }
 390 
 391     window.addEventListener('resize', onResize);
 392     onResize();
 393 
 394     textures.skyDay.object.mapping = THREE.EquirectangularRefractionMapping;
 395     textures.skyNight.object.mapping = THREE.EquirectangularRefractionMapping;
 396 
 397     if(settings.nightMode == true) {
 398         scene.background = textures.skyNight.object;
 399     } else {
 400         scene.background = textures.skyDay.object;
 401     }
 402 }
 403 
 404 function onTextureLoaded(key, texture) {
 405     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
 406     textures[key].object = texture;
 407     console.log('Texture `' + key + '` loaded');
 408 }
 409 
 410 function loadTextures(callback) {
 411     const loadingManager = new THREE.LoadingManager();
 412 
 413     loadingManager.onLoad = () => {
 414         console.log('All textures loaded');
 415         callback();
 416     };
 417 
 418     for (const key in textures) {
 419         console.log("Loading textures");
 420         const loader = new THREE.TextureLoader(loadingManager);
 421         const texture = textures[key];
 422         texture.object = loader.load(
 423             texture.url,
 424             onTextureLoaded.bind(this, key),
 425             null,
 426             (error) => {
 427                 console.error(error);
 428             }
 429         );
 430     }
 431 }
 432 
 433 function buildBridge() {
 434     // const bridge1 = generateBridge();
 435     // const bridge2 = generateBridge();
 436 
 437     // (arcCount, arcRadius, columnWidth, columnHeight, padding, squaresCount, squareLen)
 438     const bridge1 = generateBridge(1, 3, 0, 0, 10, 2, 2);
 439     const bridge2 = generateBridge(2, 2, 1, 0, 15, 3, 2);
 440 
 441     bridge1.scale.set(0.5, 0.5, 0.5);
 442     bridge1.position.set(16, -0.75, 36);
 443     // bridge1.rotateY(-Math.PI*0.118);
 444 
 445     bridge2.scale.set(0.5, 0.5, 0.5);
 446     bridge2.position.set(-14, -0.25, -41);
 447     // bridge2.rotateY(-Math.PI*0.118);
 448 
 449     const bridgeCamera = new THREE.PerspectiveCamera(
 450         55, window.innerWidth / window.innerHeight, 0.1, 10000);
 451 
 452     bridgeCamera.position.set(-18, 11, -2.75);
 453     bridgeCamera.lookAt(50, 0, 42);
 454     bridge2.add(bridgeCamera);
 455     bridgeCamera.name = "bridgeCamera";
 456     cameras.push(bridgeCamera);
 457     camerasName.push("Vista del Puente");
 458 
 459     bridge1.castShadow    = true;
 460     bridge1.receiveShadow = true;
 461     bridge2.castShadow    = true;
 462     bridge2.receiveShadow = true;
 463 
 464     scene.add(bridge1);
 465     scene.add(bridge2);
 466 
 467     objects.push(bridge1);
 468     objects.push(bridge2);
 469 }
 470 
 471 // loco -> locomotora/locomotive
 472 function buildLoco() {
 473     train = buildTrain();
 474 
 475     const trainConductorCamera = new THREE.PerspectiveCamera(
 476         55, window.innerWidth / window.innerHeight, 0.1, 10000);
 477 
 478     trainConductorCamera.position.set(0, 7, -11);
 479     trainConductorCamera.lookAt(0, 20, 100);
 480     train.add(trainConductorCamera);
 481     trainConductorCamera.name = "trainConductorCamera";
 482     cameras.push(trainConductorCamera);
 483     camerasName.push("Cabina del Tren");
 484 
 485     const trainCamera = new THREE.PerspectiveCamera(
 486         55, window.innerWidth / window.innerHeight, 0.1, 10000);
 487 
 488     trainCamera.position.set(-12, 6, -20);
 489     trainCamera.lookAt(0, 10, 15);
 490     train.add(trainCamera);
 491     trainCamera.name = `trainCamera`;
 492     cameras.push(trainCamera);
 493     camerasName.push("Costado del Tren");
 494 
 495     const trainBackCamera = new THREE.PerspectiveCamera(
 496         55, window.innerWidth / window.innerHeight, 0.1, 10000);
 497 
 498     trainBackCamera.position.set(0, 16, -10);
 499     trainBackCamera.lookAt(0, 18, -100);
 500     train.add(trainBackCamera);
 501     trainBackCamera.name = "trainBackCamera";
 502     cameras.push(trainBackCamera);
 503     camerasName.push("Vista hacia atras desde la Cabina del Tren");
 504 
 505     // SpotLight(color: Int, intensity: Float, distance: Float, angle: Radians, penumbra: Float, decay: Float)
 506     trainLight = new THREE.SpotLight(0xffffff, 200.0, 100.0, Math.PI/6, 0.5, 1.0);
 507     train.add(trainLight.target);
 508     train.add(trainLight);
 509     trainLight.position.set(0, 4, 5);
 510     trainLight.target.position.set(0, -100, 1000);
 511     trainLight.target.updateMatrixWorld();
 512 
 513     trainLight2 = new THREE.SpotLight(0xffffff, 10.0, 3.0, Math.PI/6, 0.5, 0.5);
 514     train.add(trainLight2.target);
 515     train.add(trainLight2);
 516     trainLight2.position.set(0, 3.25, 15);
 517     trainLight2.target.position.set(0, 0, -100);
 518     trainLight2.target.updateMatrixWorld();
 519 
 520     trainLight3 = new THREE.SpotLight(0xffffff, 10.0, 16.0, Math.PI/3, 0.5, 0.5);
 521     train.add(trainLight3.target);
 522     train.add(trainLight3);
 523     trainLight3.position.set(0, 5, 5);
 524     trainLight3.target.position.set(0, -25, 100);
 525     trainLight3.target.updateMatrixWorld();
 526 
 527     //Set up shadow properties for the light
 528     trainLight.castShadow            = true;
 529     trainLight.shadow.mapSize.width  = 256;
 530     trainLight.shadow.mapSize.height = 256;
 531     trainLight.shadow.camera.near    = 0.5;
 532     trainLight.shadow.camera.far     = 40;
 533     trainLight.shadow.focus          = 1;
 534 
 535     trainLight3.castShadow            = true;
 536     trainLight3.shadow.mapSize.width  = 128;
 537     trainLight3.shadow.mapSize.height = 128;
 538     trainLight3.shadow.camera.near    = 0.5;
 539     trainLight3.shadow.camera.far     = 25;
 540     trainLight3.shadow.focus          = 1;
 541 
 542     const trainLightHelper = new THREE.CameraHelper(trainLight.shadow.camera);
 543     const trainLight3Helper = new THREE.CameraHelper(trainLight3.shadow.camera);
 544 
 545     trainLight.visible = settings.nightMode;
 546     trainLight2.visible = settings.nightMode;
 547     trainLight3.visible = settings.nightMode;
 548 
 549     trainLightHelper.visible  = settings.showHelpers;
 550     trainLight3Helper.visible = settings.showHelpers;
 551 
 552     helpers.push(trainLightHelper);
 553     helpers.push(trainLight3Helper);
 554 
 555     train.scale.set(0.145, 0.145, 0.145);
 556     train.visible = settings.showTrain;
 557     scene.add(train);
 558 }
 559 
 560 function buildRailsFoundation() {
 561     const railsFoundationGeometry = buildRailsFoundationGeometry();
 562 
 563     textures.durmientes.object.wrapS = THREE.RepeatWrapping;
 564     textures.durmientes.object.wrapT = THREE.RepeatWrapping;
 565     textures.durmientes.object.repeat.set(1, 150);
 566     textures.durmientes.object.anisotropy = 16;
 567 
 568     // load into `map` the example texture
 569     // const map = new THREE.TextureLoader().load(
 570     //     'https://threejs.org/examples/textures/uv_grid_opengl.jpg');
 571     // map.wrapS = map.wrapT = THREE.RepeatWrapping;
 572     // map.repeat.set(1, 80);
 573     // map.anisotropy = 16;
 574     // map.rotation = Math.PI/2;
 575 
 576     const railsFoundationMaterial = new THREE.MeshPhongMaterial({
 577         side: THREE.FrontSide,
 578         map: textures.durmientes.object
 579         // map: map
 580     });
 581 
 582     const railsFoundation = new THREE.Mesh(railsFoundationGeometry, railsFoundationMaterial);
 583     railsFoundation.receiveShadow = true;
 584     railsFoundation.castShadow    = true;
 585     railsFoundation.position.set(-1, 1.25, -1);
 586     railsFoundation.scale.set(1.00, 1.50, 1.00);
 587     scene.add(railsFoundation);
 588     // descomentando esto se tiene en cuenta la altura del terraplen de las vias
 589     // para la camara en primera person pero resulta en muy baja performance
 590     // objects.push(railsFoundation);
 591 }
 592 
 593 function buildRails() {
 594     const railsGeometry = buildRailsGeometry();
 595     const railsMaterial = new THREE.MeshPhongMaterial({
 596         side: THREE.BackSide,
 597         color: 0xFFFFFF
 598     });
 599 
 600     const rails = new THREE.Mesh(railsGeometry, railsMaterial);
 601     rails.castShadow = true;
 602     rails.receiveShadow = true;
 603     rails.position.set(-1, 1.25, -1);
 604     rails.scale.set(1.00, 1.50, 1.00);
 605     scene.add(rails);
 606 }
 607 
 608 function buildTerrainCustomMaterial() {
 609     const customMaterial = new THREE.MeshPhongMaterial({
 610         color: 0xffffff,
 611         specular: 0x333333,
 612         side: THREE.FrontSide,
 613     });
 614 
 615     // definos las variables uniformes adicionales que necesitamos
 616     let additionalUniforms = {
 617         // grassTexture: { value: grassTexture, type: 't' },
 618         // rockTexture: { value: rockTexture, type: 't' },
 619 
 620         dirtSampler: { type: 't', value: textures.tierra.object },
 621         rockSampler: { type: 't', value: textures.roca.object },
 622         grassSampler: { type: 't', value: textures.pasto.object },
 623         scale: { type: 'f', value: 3.0 },
 624         terrainAmplitude: { type: 'f', value: amplitude },
 625         terrainAmplitudeBottom: { type: 'f', value: amplitudeBottom },
 626         worldNormalMatrix: { type: 'm4', value: null },
 627         dirtStepWidth: { type: 'f', value: 0.20 },
 628         rockStepWidth: { type: 'f', value: 0.15 },
 629     };
 630     // le decimos al material que vamos a usar UVs para que incluya las coordenadas UV en el shader
 631     customMaterial.defines = { USE_UV: true };
 632 
 633     // Este callback se ejecuta antes de compilar el shader
 634     // Hay que ver como referencia el archivo
 635     // node_modules/three/src/renderers/shaders/ShaderLib/meshphong.glsl.js
 636     // para saber que chunks podemos reemplazar
 637 
 638     customMaterial.onBeforeCompile = function (shader) {
 639         // le agregamos las variables uniformes adicionales al shader
 640         shader.uniforms.dirtSampler = additionalUniforms.dirtSampler;
 641         shader.uniforms.rockSampler = additionalUniforms.rockSampler;
 642         shader.uniforms.grassSampler = additionalUniforms.grassSampler;
 643         shader.uniforms.scale = additionalUniforms.scale;
 644         shader.uniforms.terrainAmplitude = additionalUniforms.terrainAmplitude;
 645         shader.uniforms.terrainAmplitudeBottom = additionalUniforms.terrainAmplitudeBottom;
 646         shader.uniforms.worldNormalMatrix = additionalUniforms.worldNormalMatrix;
 647         shader.uniforms.dirtStepWidth = additionalUniforms.dirtStepWidth;
 648         shader.uniforms.rockStepWidth = additionalUniforms.rockStepWidth;
 649 
 650         // hacemos un search and replace en el vertex shader
 651         // buscamos la linea que dice
 652         // vViewPosition = - mvPosition.xyz;
 653         // y le agregamos una linea mas que guarde la posicion del vertice en el espacio del mundo
 654         shader.vertexShader = shader.vertexShader.replace(
 655             'vViewPosition = - mvPosition.xyz;',
 656             `vViewPosition = - mvPosition.xyz;
 657              vWorldPosition = (modelMatrix*vec4(transformed,1.0)).xyz;`
 658         );
 659 
 660         // agregamos una variable varying al comienzo del vertex shader
 661         // para pasar la posicion del vertice en coordenadas del mundo al fragment shader
 662         shader.vertexShader =
 663             `varying vec3 vWorldPosition;
 664         ` + shader.vertexShader;
 665 
 666         // agregamos las variables uniformes y varying al fragment shader
 667         // Siempre hay que tener cuidado con los nombres de las variables que definimos
 668         // no deben coincidir con las variables que usa Three.js
 669 
 670         shader.fragmentShader = `
 671 uniform float scale;
 672 uniform float terrainAmplitude;
 673 uniform float terrainAmplitudeBottom;
 674 uniform float dirtStepWidth;
 675 uniform float rockStepWidth;
 676 
 677 uniform sampler2D dirtSampler;
 678 uniform sampler2D rockSampler;
 679 uniform sampler2D grassSampler;
 680 varying vec3 vWorldPosition;
 681 
 682 ` + shader.fragmentShader;
 683 
 684         shader.fragmentShader = shader.fragmentShader.replace(
 685             'void main() {',
 686             `
 687 float myNormalizeFunc(float inputValue, float minValue, float maxValue) {
 688     return (inputValue - minValue) / (maxValue - minValue);
 689 }
 690 
 691 void main () {
 692     float heightFactor = vWorldPosition.y - terrainAmplitudeBottom;
 693     float heightFactorNormalized = myNormalizeFunc(heightFactor, 0.0, terrainAmplitude);
 694 `);
 695 
 696         // reemplazamos el include del chunk map_fragment por nuestro propio codigo
 697         shader.fragmentShader = shader.fragmentShader.replace(
 698             '#include <map_fragment>',
 699             `// calculamos las coordenadas UV en base a las coordenadas de mundo
 700 vec2 uvCoords=vWorldPosition.xz/100.0;
 701 vec2 myUV  = uvCoords*8.0;
 702 vec2 myUV2 = uvCoords*scale;
 703 
 704 vec3 grass = texture2D(grassSampler, uvCoords).xyz;
 705 vec3 dirt  = texture2D(dirtSampler, uvCoords*4.0).xyz;
 706 vec3 rock  = texture2D(rockSampler, uvCoords).xyz;
 707 
 708 // si quisieramos podriamos usar la variabl vUv tambien que son las coordenadas UV del vertice
 709 
 710 // muestreo de pasto a diferentes escalas, luego se combina con \`mix()\`
 711 vec3 grass1 = texture2D(grassSampler, myUV2*1.00).xyz;
 712 vec3 grass2 = texture2D(grassSampler, myUV2*3.13).xyz;
 713 vec3 grass3 = texture2D(grassSampler, myUV2*2.37).xyz;
 714 vec3 colorGrass = mix(mix(grass1,grass2,0.5),grass3,0.3);
 715 
 716 // lo mismo para la textura de tierra
 717 vec3 dirt1 = texture2D(dirtSampler, myUV2*3.77).xyz;
 718 vec3 dirt2 = texture2D(dirtSampler, myUV2*1.58).xyz;
 719 vec3 dirt3 = texture2D(dirtSampler, myUV2*1.00).xyz;
 720 vec3 colorDirt = mix(mix(dirt1, dirt2, 0.5), dirt3, 0.3);
 721 
 722 // lo mismo para la textura de roca
 723 vec3 rock1 = texture2D(rockSampler,myUV2*0.40).xyz;
 724 vec3 rock2 = texture2D(rockSampler,myUV2*2.38).xyz;
 725 vec3 rock3 = texture2D(rockSampler,myUV2*3.08).xyz;
 726 vec3 colorRock = mix(mix(rock1, rock2, 0.5), rock3,0.5);
 727 
 728 float u = heightFactorNormalized;
 729 
 730 float width2 = rockStepWidth;
 731 float rockFactor = 2.00 - smoothstep(0.0, width2, u) - smoothstep(1.0, 1.00 - width2, u);
 732 
 733 float width = dirtStepWidth;
 734 float s1 = smoothstep(0.00, width, u);
 735 float s2 = smoothstep(width, width*2.0, u);
 736 float s3 = smoothstep(0.50, 0.50 + width, u);
 737 float s4 = smoothstep(0.50 + width, 0.50 + width*2.0, u);
 738 float dirtFactor = (s1 - s2) + (s3 - s4);
 739 
 740 float grassFactor = smoothstep(0.0, 0.35, u) - smoothstep(0.35, 1.00, u);
 741 
 742 vec3 colorDirtGrass = mix(colorDirt, colorGrass, grassFactor);
 743 vec3 colorDirtGrassDirt = mix(colorDirtGrass, colorDirt, dirtFactor);
 744 vec3 color = mix(colorDirtGrassDirt, colorRock, rockFactor);
 745 
 746 diffuseColor = vec4(color, 1.0);
 747 
 748 // leemos los colores de las texturas
 749 // vec4 grassColor = texture2D(grassSampler,uvCoords);
 750 // vec4 rockColor = texture2D(rockSampler,uvCoords);
 751 
 752 // mezclamos los colores en base a la altura del vertice
 753 // diffuseColor = mix(grassColor, rockColor, smoothstep(0.0,5.0,vWorldPosition.y));`);
 754 
 755         // imprimimos el shader para debuggear
 756         // console.log(shader.vertexShader);
 757         // console.log(shader.fragmentShader);
 758     };
 759     return customMaterial;
 760 }
 761 
 762 function buildTerrain() {
 763     const width = 100;
 764     const height = 100;
 765 
 766     terrainGeometry = elevationGeometry(
 767         width, height,
 768         amplitude,
 769         widthSegments, heightSegments,
 770         textures.elevationMap.object);
 771 
 772     console.log('Applying textures');
 773 
 774     const customMaterial = buildTerrainCustomMaterial();
 775     terrain = new THREE.Mesh(terrainGeometry, customMaterial);
 776 
 777     terrain.castShadow = true;
 778     terrain.receiveShadow = true;
 779 
 780     scene.add(terrain);
 781 
 782     terrain.position.set(0, amplitudeBottom, 0);
 783     objects.push(terrain);
 784 
 785     console.log('Generating water');
 786     const waterGeometry = new THREE.PlaneGeometry(width/2, height-1.25);
 787     const waterMaterial = new THREE.MeshPhongMaterial( {color: 0x12ABFF, side: THREE.BackSide} );
 788     const water = new THREE.Mesh( waterGeometry, waterMaterial );
 789     water.rotateX(Math.PI/2);
 790     water.position.set(0, 0, -0.65);
 791 
 792     water.castShadow    = false;
 793     water.receiveShadow = true;
 794     scene.add(water);
 795 }
 796 
 797 function buildTunnel() {
 798     // tunnelHeight = 20, tunnelWidth = 14, tunnelWallThickness = 0.5, tunnelLen = 26
 799     const tunnelGeometry = generateTunnelGeometry(24, 12, 0.5, 46);
 800 
 801     textures.madera.object.wrapS = THREE.RepeatWrapping;
 802     textures.madera.object.wrapT = THREE.RepeatWrapping;
 803     textures.madera.object.repeat.set(0.10, 0.10);
 804     textures.madera.object.anisotropy = 16;
 805 
 806     const tunnelMaterial = new THREE.MeshPhongMaterial({
 807         side: THREE.FrontSide,
 808         map: textures.madera.object
 809     });
 810 
 811     const tunnel = new THREE.Mesh(tunnelGeometry, tunnelMaterial) ;
 812     tunnel.castShadow = true;
 813     tunnel.receiveShadow = true;
 814     tunnel.scale.set(0.5, 0.5, 0.5);
 815 
 816     const trainPathPos = getRailsPathPosAt(0.32);
 817     tunnel.position.set(trainPathPos[0].x, 0, trainPathPos[0].z);
 818     tunnel.lookAt(trainPathPos[1].x*1000, 0, trainPathPos[1].z*1000);
 819 
 820     scene.add(tunnel);
 821 
 822     const tunnelCamera = new THREE.PerspectiveCamera(
 823         65, window.innerWidth / window.innerHeight, 0.1, 10000);
 824 
 825     tunnelCamera.position.set(-1, 12, 18);
 826     tunnelCamera.lookAt(0, 10, -10);
 827     tunnelCamera.name = "tunnelCamera";
 828     tunnel.add(tunnelCamera);
 829     cameras.push(tunnelCamera);
 830     camerasName.push("Camara del Tunel");
 831 }
 832 
 833 function buildTrees(count = 50) {
 834     const [treeLogs, treeLeaves] = createInstancedTrees(count);
 835     scene.add(treeLogs);
 836     scene.add(treeLeaves);
 837 
 838     treeLogs.castShadow    = true;
 839     treeLogs.receiveShadow = true;
 840     treeLeaves.castShadow    = true;
 841     treeLeaves.receiveShadow = true;
 842 }
 843 
 844 function toggleNightMode() {
 845     console.log("Toggling night mode");
 846     console.log(settings.nightMode);
 847     if(settings.nightMode == true) {
 848         lights.ambient.object.visible = false;
 849         lights.hemisphere.object.intensity = 0;
 850         lights.directional.object.color.setHex(0xcdddfe); // 0x090254; 0xa8a1fd
 851         scene.background = textures.skyNight.object;
 852         lights.directional.object.position.set(35, 35, 35); // match the skybox texture moon light
 853         trainLight.visible = true;
 854         trainLight2.visible = true;
 855         trainLight3.visible = true;
 856     } else {
 857         lights.ambient.object.visible = true;
 858         lights.hemisphere.object.intensity = 1;
 859         lights.directional.object.intensity = 1;
 860         lights.directional.object.color.setHex(0xFFFFFF);
 861         scene.background = textures.skyDay.object;
 862         lights.directional.object.position.set(-35, 35, 35);
 863         trainLight.visible = false;
 864         trainLight2.visible = false;
 865         trainLight3.visible = false;
 866     }
 867 }
 868 
 869 function createMenu() {
 870     gui = new dat.GUI({ width: 250 });
 871     gui.add(settings, 'animationEnable', true).name('Animaciones');
 872     gui.add(settings, 'showTrain').name('Mostrar tren').onChange(
 873         function () {
 874             train.visible = !train.visible;
 875         }
 876     );
 877     gui.add(settings, 'nightMode', false).name('Modo noche').onChange(toggleNightMode);
 878     gui.add(settings, 'showHelpers', true).name('Mostrar Guias').onChange(
 879         function() {
 880             for(let i = 0; i < helpers.length; ++i) {
 881                 helpers[i].visible = settings.showHelpers;
 882                 scene.add(helpers[i]);
 883             }
 884         }
 885     );
 886     gui.add(settings, 'showFps', true).name('Mostrar FPS').onChange(
 887         function() {
 888             if(settings.showFps == true) {
 889                 document.body.appendChild(stats.dom);
 890             } else {
 891                 document.body.removeChild(stats.dom);
 892             }
 893         }
 894     );
 895     gui.add(settings, 'shadows', true).name('Sombras').onChange(
 896         function() {
 897             renderer.shadowMap.enabled = settings.shadows;
 898             scene.traverse(function (child) {
 899                 if (child.material) {
 900                     child.material.needsUpdate = true
 901                 }
 902             });
 903         }
 904     );
 905     gui.add(settings, "currCameraName", camerasName).name('Camara').setValue(camerasName[settings.currCameraIndex]).onChange(
 906         function() {
 907             settings.currCameraIndex = camerasName.indexOf(settings.currCameraName);
 908             updateCamera();
 909         }
 910     );
 911 }
 912 
 913 function buildScene() {
 914     console.log('Building scene');
 915     buildTunnel();
 916     buildTrees(200);
 917     buildTerrain();
 918     buildRailsFoundation();
 919     buildRails();
 920     buildLoco();
 921     buildBridge();
 922 }
 923 
 924 function mainLoop() {
 925     requestAnimationFrame(mainLoop);
 926     stats.begin();
 927 
 928     const dt = 0.001;
 929     if(settings.animationEnable) {
 930         time = (time < 1.0-dt) ? (time + dt) : 0.00;
 931     }
 932 
 933     if(train.visible) {
 934         updateTrainCrankPosition(time*200);
 935         const trainPos = getRailsPathPosAt(time);
 936         const railsData = getRailsPathPosAt(time);
 937 
 938         let x = railsData[0].x;
 939         let z = railsData[0].z;
 940 
 941         train.position.set(-1+x, 2.30, -1+z);
 942         train.lookAt(railsData[1].x*1000, 1.9, railsData[1].z*1000);
 943     }
 944 
 945     let time2 = performance.now();
 946     const firstPersonCameraHeight = 1.60;
 947     if (firstPersonControls.isLocked === true) {
 948         raycaster = new THREE.Raycaster();
 949         var raycasterPos = new THREE.Vector3();
 950         raycasterPos.copy(firstPersonControls.getObject().position)
 951         raycasterPos.y += 2;
 952         var raycasterDir = new THREE.Vector3(0, -1, 0);
 953 
 954         raycaster.set(raycasterPos, raycasterDir);
 955         const intersections = raycaster.intersectObjects(objects);
 956         let positionY;
 957         if((intersections == undefined) || (intersections[0] == undefined)) {
 958             positionY = 0.0;
 959         } else {
 960             positionY = intersections[0].point.y;
 961         }
 962 
 963         const delta = (time2 - prevTime) / 1000;
 964 
 965         velocity.x -= velocity.x * 11.0 * delta;
 966         velocity.z -= velocity.z * 11.0 * delta;
 967         velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
 968 
 969         direction.z = Number( moveForward ) - Number( moveBackward );
 970         direction.x = Number( moveRight ) - Number( moveLeft );
 971         direction.normalize(); // this ensures consistent movements in all directions
 972 
 973         if (moveForward || moveBackward) {
 974             if(moveForwardRunning) {
 975                 velocity.z -= direction.z * 200.0 * delta;
 976             } else {
 977                 velocity.z -= direction.z * 100.0 * delta;
 978             }
 979         }
 980 
 981         if (moveLeft || moveRight) {
 982             velocity.x -= direction.x * 100.0 * delta;
 983         }
 984 
 985         // TODO: terrain limits
 986         firstPersonControls.moveRight(-velocity.x * delta);
 987         firstPersonControls.moveForward(-velocity.z * delta);
 988 
 989         firstPersonControls.getObject().position.y =
 990             positionY < 0.0 ? firstPersonCameraHeight : positionY + firstPersonCameraHeight;
 991 
 992 
 993         if (firstPersonControls.getObject().position.y < (positionY + firstPersonCameraHeight)) {
 994             velocity.y = 0;
 995             firstPersonControls.getObject().position.y = firstPersonCameraHeight;
 996             // canJump = true;
 997         }
 998     }
 999 
1000     prevTime = time2;
1001     renderer.render(scene, cameras[settings.currCameraIndex]);
1002     stats.end();
1003 }
1004 
1005 function main() {
1006     setupThreeJs();
1007     setupFirstPersonControls();
1008     time = 0.90;
1009     prevTime = performance.now();
1010     buildScene();
1011     createMenu();
1012     updateCamera();
1013     mainLoop();
1014 }
1015 
1016 loadTextures(main);