TA159

Notas, resueltos y trabajos practicos de la materia Sistemas Gráficos
Index Commits Files Refs Submodules README LICENSE
tp/src/standalone/track-map.js (13618B)
   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 { vertexShader, fragmentShader } from '/src/shaders.js';
   5 
   6 let scene, camera, renderer, container, terrainMaterial, terrainGeometry, terrain;
   7 let treesForbiddenMapData, treesForbiddenMap, elevationMap, elevationMapData;
   8 
   9 const widthSegments   = 100;
  10 const heightSegments  = 100;
  11 const amplitude       = 8;
  12 const amplitudeBottom = -1.00;
  13 
  14 import tierraUrl           from '../assets/tierra.jpg'
  15 import rocaUrl             from '../assets/roca.jpg'
  16 import pastoUrl            from '../assets/pasto.jpg'
  17 import elevationMapUrl     from '../assets/elevation_map_wider_river.png'
  18 import treeForbiddenMapUrl from '../assets/tree_forbidden_zone_map_wider_path.png'
  19 
  20 const textures = {
  21     tierra:           { url: tierraUrl,           object: null },
  22     roca:             { url: rocaUrl,             object: null },
  23     pasto:            { url: pastoUrl,            object: null },
  24     elevationMap:     { url: elevationMapUrl,     object: null },
  25     treeForbiddenMap: { url: treeForbiddenMapUrl, object: null }
  26 };
  27 
  28 function onResize() {
  29     camera.aspect = container.offsetWidth / container.offsetHeight;
  30     camera.updateProjectionMatrix();
  31     renderer.setSize(container.offsetWidth, container.offsetHeight);
  32 }
  33 
  34 function setupThreeJs() {
  35     scene = new THREE.Scene();
  36     container = document.getElementById('mainContainer');
  37 
  38     renderer = new THREE.WebGLRenderer();
  39     renderer.setClearColor(0x606060);
  40     container.appendChild(renderer.domElement);
  41 
  42     camera = new THREE.PerspectiveCamera(
  43         35, window.innerWidth/window.innerHeight, 0.1, 1000);
  44     camera.position.set(100, 120, -100);
  45     camera.lookAt(0, 0, 0);
  46 
  47     const controls = new OrbitControls(camera, renderer.domElement);
  48 
  49     const ambientLight = new THREE.AmbientLight(0xffffff);
  50     scene.add(ambientLight);
  51 
  52     const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
  53     //scene.add(hemisphereLight);
  54 
  55     const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  56     directionalLight.position.set(100, 100, 100);
  57     scene.add(directionalLight);
  58 
  59     const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
  60     // scene.add(directionalLightHelper);
  61 
  62      const gridHelper = new THREE.GridHelper(150, 150);
  63      scene.add(gridHelper);
  64 
  65     const axesHelper = new THREE.AxesHelper( 5 );
  66     scene.add( axesHelper );
  67 
  68     window.addEventListener('resize', onResize);
  69     onResize();
  70 }
  71 
  72 const imgWidth  = 512;
  73 const imgHeight = 512;
  74 
  75 // (x, y) ∈ [imgHeight, imgWidth] -> son un punto de la imagen 
  76 function getPixelIndex(x, y) {
  77     return Math.floor(x + y*imgWidth*4);
  78 }
  79 
  80 function getPixel(imgData, index) {
  81   let i = index*4, d = imgData.data
  82   return [d[i],d[i+1],d[i+2],d[i+3]] // Returns array [R,G,B,A]
  83 }
  84 
  85 function getPixelXY(imgData, x, y) {
  86   return getPixel(imgData, y*imgData.width+x)
  87 }
  88 
  89 // position: Vector3
  90 function isForbbidenPosition(position) {
  91     const x = Math.floor(position.x);
  92     const y = position.y;
  93     const z = Math.floor(position.z);
  94 
  95     if((y > 5.0) || (y < 2.65)){
  96         console.log("(" + position.x + ", " + position.y + ", " + position.z + ") is not valid ");
  97         return true;
  98     }
  99     
 100     let pixelArray = getPixelXY(treesForbiddenMap, x, z);
 101     const R = pixelArray[0]; // Red
 102     const G = pixelArray[1]; // Green
 103     const B = pixelArray[2]; // Blue
 104     const A = pixelArray[3]; // Alpha
 105     // const pixel = new THREE.Vector4(R, G, B, A);
 106 
 107     if(((R <= 10) && (G >= 250) && (B <= 10))
 108         || (R <= 80) && (G <= 80) && (B <= 80)
 109         || (R >= 200) && (G >= 200) && (B >= 200)) {
 110         // console.log("(" + position.x + ", " + position.y + ", " + position.z + ") is not valid ");
 111         return true;
 112     }
 113 
 114     console.log("(" + position.x + ", " + position.y + ") is valid ");
 115     return false;
 116 }
 117 
 118 // obtiene una posicion aleatoria en el terreno, para obtener la altura del
 119 // terreno utiliza el mapa de elevacion.
 120 // `padding` permite definir un borde del cual no se toman puntos
 121 function getRandomPositionInTerrain(padding = 0) {
 122     const x = Math.floor(Math.random() * (widthSegments-(padding*2)));
 123     const z = Math.floor(Math.random() * (heightSegments-(padding*2)));
 124 
 125     const pixelArray = getPixelXY(elevationMap, x, z); // array [R,G,B,A]
 126     const y = (pixelArray[0]/255)*amplitude;
 127 
 128     const position = new THREE.Vector3(x+padding, y, z+padding);
 129     return position;
 130 }
 131 
 132 function createInstancedTrees(count) {
 133     console.log('Generating `' + count + '` instances of tree');
 134 
 135     let logHeight = 4.0;
 136     const treeLogGeometry   = new THREE.CylinderGeometry(
 137         0.30, 0.30, logHeight, 40, 40);
 138     treeLogGeometry.translate(0, logHeight/2.0, 0);
 139     const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry();
 140     instancedTreeLogGeometry.copy(treeLogGeometry);
 141     const treeLogMaterial   = new THREE.MeshPhongMaterial({color: 0x7c3f00});
 142     const instancedTreeLogs = new THREE.InstancedMesh(
 143         instancedTreeLogGeometry,
 144         treeLogMaterial,
 145         count);
 146 
 147     const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
 148     const instancedTreeLeavesGeometry = new THREE.InstancedBufferGeometry();
 149     instancedTreeLeavesGeometry.copy(treeLeavesGeometry);
 150     const treeLeavesMaterial  = new THREE.MeshPhongMaterial({color: 0x365829});
 151     const instancedTreeLeaves = new THREE.InstancedMesh(
 152         instancedTreeLeavesGeometry,
 153         treeLeavesMaterial,
 154         count);
 155 
 156     const rotMatrix         = new THREE.Matrix4();
 157     const translationMatrix = new THREE.Matrix4();
 158     const treeLogMatrix     = new THREE.Matrix4();
 159     const treeLeavesMatrix  = new THREE.Matrix4();
 160 
 161     const treesBorderPadding = 3.0;
 162     for (let i = 0; i < count; i++) {
 163         let position = getRandomPositionInTerrain(treesBorderPadding);
 164         for(let j = 0; isForbbidenPosition(position); ++j) {
 165             position = getRandomPositionInTerrain(treesBorderPadding);
 166             if(j++ == 1000) { // maximo de iteraciones
 167                 break;
 168             }
 169         }
 170 
 171         if(isForbbidenPosition(position)) {
 172             continue;
 173         }
 174 
 175         const treeOffset = 0.25;
 176         // 1.50 numbero magico para posicionar correctamente los arboles con
 177         // respecto al terreno
 178         position.x -= (widthSegments+treesBorderPadding+1.50)/2;
 179         position.y += amplitudeBottom - treeOffset;
 180         position.z -= (heightSegments+treesBorderPadding)/2;
 181         translationMatrix.makeTranslation(position);
 182         treeLogMatrix.identity();
 183         treeLeavesMatrix.identity();
 184 
 185         let scale = 0.6 + (Math.random()*(logHeight/3));
 186         treeLogMatrix.makeScale(1, scale, 1);
 187         treeLogMatrix.premultiply(translationMatrix);
 188 
 189         position.y += scale*logHeight;
 190         translationMatrix.makeTranslation(position);
 191         treeLeavesMatrix.premultiply(translationMatrix);
 192 
 193         instancedTreeLogs.setMatrixAt(i, treeLogMatrix);
 194         instancedTreeLeaves.setMatrixAt(i, treeLeavesMatrix);
 195     }
 196 
 197     console.log('Done generating `' + count + '` instances of tree');
 198     return [instancedTreeLogs, instancedTreeLeaves];
 199 }
 200 
 201 
 202 // La funcion devuelve una geometria de Three.js
 203 // width: Ancho del plano
 204 // height: Alto del plano
 205 // amplitude: Amplitud de la elevacion
 206 // widthSegments: Numero de segmentos en el ancho
 207 // heightSegments: Numero de segmentos en el alto
 208 function elevationGeometry(width, height, amplitude, widthSegments, heightSegments) {
 209     console.log('Generating terrain geometry');
 210     let geometry = new THREE.BufferGeometry();
 211 
 212     const positions = [];
 213     const indices = [];
 214     const normals = [];
 215     const uvs = [];
 216 
 217     let imageData = elevationMap;
 218     let data = elevationMapData;
 219 
 220     const quadsPerRow = widthSegments - 1;
 221 
 222     // Recorremos los segmentos horizontales y verticales
 223     for (let x = 0; x < widthSegments - 1; x++) {
 224         for (let y = 0; y < heightSegments - 1; y++) {
 225             // valor del pixel en el mapa de elevacion en la posicion i, j
 226 
 227             let pixel = getPixelXY(imageData, x, y);
 228 
 229             // se utiliza el canal rojo [R, G, B, A];
 230             let z0 = pixel[0] / 255;
 231             const z = amplitude * z0;
 232 
 233             // valores de los píxeles de los puntos adyacentes
 234             let xPrev, xNext, yPrev, yNext;
 235 
 236             xPrev = (x > 0) ? getPixelXY(imageData, x-1, y)[0]/255 : undefined;
 237             xNext = (x < widthSegments-1) ? getPixelXY(imageData, x+1, y)[0]/255 : undefined;
 238 
 239             yPrev = (y > 0) ? getPixelXY(imageData, x, y+1)[0]/255 : undefined;
 240             yNext = (y < heightSegments-1) ? getPixelXY(imageData, x, y+1)[0]/255 : undefined;
 241 
 242             // diferencia entre los valores de los píxeles adyacentes en el eje
 243             // `x` y en el eje `y` de la imagen en el espacio de la textura
 244             let deltaX;
 245             if (xPrev == undefined) {
 246                 deltaX = xNext - z0;
 247             } else if (yNext == undefined) {
 248                 deltaX = xPrev - z0;
 249             } else {
 250                 deltaX = (xNext - xPrev) / 2;
 251             }
 252 
 253             let deltaY;
 254             if (yPrev == undefined) {
 255                 deltaY = yNext - z0;
 256             } else if (yNext == undefined) {
 257                 deltaY = yPrev - z0;
 258             } else {
 259                 deltaY = (yNext - yPrev) / 2;
 260             }
 261 
 262             // Añadimos los valores de los puntos al array de posiciones
 263             positions.push((width * x) / widthSegments - width / 2);
 264             positions.push(z);
 265             positions.push((height * y) / heightSegments - height / 2);
 266 
 267             // vectores tangentes a la superficie en el eje `x` y en el eje `y`
 268             let tanX = new THREE.Vector3(width / widthSegments, deltaX * amplitude, 0);
 269             let tanY = new THREE.Vector3(0, deltaY * amplitude, height / heightSegments);
 270 
 271             tanX.normalize();
 272             tanY.normalize();
 273 
 274             let normal = new THREE.Vector3().crossVectors(tanY, tanX);
 275 
 276             normals.push(normal.x);
 277             normals.push(normal.y);
 278             normals.push(normal.z);
 279 
 280             uvs.push(x / (widthSegments - 1));
 281             uvs.push(y / (heightSegments - 1));
 282 
 283             if ((x == widthSegments-2) || (y == heightSegments-2)) continue;
 284 
 285             // Ensamblamos los triangulos
 286             indices.push(x + y*quadsPerRow);
 287             indices.push(x + 1 + y*quadsPerRow);
 288             indices.push(x + 1 + (y+1)*quadsPerRow);
 289 
 290             indices.push(x + y*quadsPerRow);
 291             indices.push(x + 1 + (y+1)*quadsPerRow);
 292             indices.push(x + (y+1)*quadsPerRow);
 293         }
 294     }
 295 
 296     geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
 297     geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
 298     geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
 299     geometry.setIndex(indices);
 300 
 301     return geometry;
 302 }
 303 
 304 function buildScene() {
 305     console.log('Building scene');
 306 
 307     const width  = widthSegments;
 308     const height = heightSegments;
 309 
 310     terrainGeometry = elevationGeometry(
 311         width, height,
 312         amplitude,
 313         widthSegments, heightSegments);
 314 
 315     console.log('Applying textures');
 316     terrainMaterial = new THREE.RawShaderMaterial({
 317         uniforms: {
 318             dirtSampler: { type: 't', value: textures.tierra.object },
 319             rockSampler: { type: 't', value: textures.roca.object },
 320             grassSampler: { type: 't', value: textures.pasto.object },
 321             scale: { type: 'f', value: 3.0 },
 322             terrainAmplitude: { type: 'f', value: amplitude },
 323             terrainAmplitudeBottom: { type: 'f', value: amplitudeBottom },
 324             worldNormalMatrix: { type: 'm4', value: null },
 325             dirtStepWidth: { type: 'f', value: 0.20 },
 326             rockStepWidth: { type: 'f', value: 0.15 },
 327         },
 328         vertexShader: vertexShader,
 329         fragmentShader: fragmentShader,
 330         side: THREE.DoubleSide,
 331     });
 332     terrainMaterial.needsUpdate = true;
 333 
 334     terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
 335     terrain.position.set(0, amplitudeBottom, 0);
 336     scene.add(terrain);
 337 
 338     const waterGeometry = new THREE.PlaneGeometry(width/2, height-1.55);
 339     const waterMaterial = new THREE.MeshPhongMaterial( {color: 0x12ABFF, side: THREE.DoubleSide} );
 340     const water = new THREE.Mesh( waterGeometry, waterMaterial );
 341     water.rotateX(Math.PI/2);
 342     water.position.set(0, 0.75, -1.00);
 343     scene.add(water);
 344 
 345     const [treeLogs, treeLeaves] = createInstancedTrees(250);
 346     scene.add(treeLogs);
 347     scene.add(treeLeaves);
 348 }
 349 
 350 function onTextureLoaded(key, texture) {
 351     texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
 352     textures[key].object = texture;
 353     console.log('Texture `' + key + '` loaded');
 354 }
 355 
 356 function loadTextures(callback) {
 357     const loadingManager = new THREE.LoadingManager();
 358 
 359     loadingManager.onLoad = () => {
 360         console.log('All textures loaded');
 361         callback();
 362     };
 363 
 364     for (const key in textures) {
 365         console.log("Loading textures");
 366         const loader = new THREE.TextureLoader(loadingManager);
 367         const texture = textures[key];
 368         texture.object = loader.load(
 369             texture.url,
 370             onTextureLoaded.bind(this, key),
 371             null,
 372             (error) => {
 373                 console.error(error);
 374             }
 375         );
 376     }
 377 }
 378 
 379 function createMenu() {
 380     const gui = new dat.GUI({ width: 400 });
 381     gui.add(terrainMaterial.uniforms.scale, 'value', 1.00, 5.00).name('Terrain texture scale');
 382     gui.add(terrainMaterial.uniforms.dirtStepWidth, 'value', 0.0, 1.0).name('dirt step width');
 383     gui.add(terrainMaterial.uniforms.rockStepWidth, 'value', 0.10, 0.50).name('rock step width');
 384 }
 385 
 386 function mainLoop() {
 387     requestAnimationFrame(mainLoop);
 388     renderer.render(scene, camera);
 389 }
 390 
 391 function loadMapsData() {
 392     console.log("Loading maps data");
 393 
 394     // Creamos un canvas para poder leer los valores de los píxeles de la textura
 395     let canvas = document.createElement('canvas');
 396     let ctx = canvas.getContext('2d');
 397 
 398     let treesForbiddenMapImage = textures.treeForbiddenMap.object.image;
 399     let elevationMapImage = textures.elevationMap.object.image;
 400 
 401     // ambos mapas deben tener el mismo tamaño
 402     const imgWidth  = widthSegments;
 403     const imgHeight = heightSegments;
 404 
 405     canvas.width  = imgWidth;
 406     canvas.height = imgHeight;
 407 
 408     ctx.drawImage(treesForbiddenMapImage, 0, 0, imgWidth, imgHeight);
 409     treesForbiddenMap = ctx.getImageData(0, 0, imgWidth, imgHeight);
 410     treesForbiddenMapData = treesForbiddenMap.data;
 411 
 412     ctx.drawImage(elevationMapImage, 0, 0, imgWidth, imgHeight);
 413     elevationMap = ctx.getImageData(0, 0, imgWidth, imgHeight);
 414     elevationMapData = elevationMap.data
 415 
 416     console.log("All maps data loaded succesfully");
 417 }
 418 
 419 function main() {
 420     loadMapsData();
 421     buildScene();
 422     // getTrainTrackShape();
 423     mainLoop();
 424 }
 425 
 426 setupThreeJs();
 427 loadTextures(main);