1 import * as THREE from 'three'; 2 3 const widthSegments = 100; 4 const heightSegments = 100; 5 const amplitude = 8; 6 const amplitudeBottom = -1.00; 7 8 import rocaUrl from './assets/roca.jpg' 9 import pastoUrl from './assets/pasto.jpg' 10 import tierraUrl from './assets/tierra.jpg' 11 import elevationMapUrl from './assets/elevation_map_wider_river.png' 12 13 const textures = { 14 tierra: { url: tierraUrl, object: null }, 15 roca: { url: rocaUrl, object: null }, 16 pasto: { url: pastoUrl, object: null }, 17 elevationMap: { url: elevationMapUrl, object: null }, 18 }; 19 20 // La funcion devuelve una geometria de Three.js 21 // width: Ancho del plano 22 // height: Alto del plano 23 // amplitude: Amplitud de la elevacion 24 // widthSegments: Numero de segmentos en el ancho 25 // heightSegments: Numero de segmentos en el alto 26 // texture: Textura que se usara para la elevacion 27 export function elevationGeometry(width, height, amplitude, widthSegments, heightSegments, texture) { 28 console.log('Generating terrain geometry'); 29 let geometry = new THREE.BufferGeometry(); 30 31 const positions = []; 32 const indices = []; 33 const normals = []; 34 const uvs = []; 35 36 // Creamos un canvas para poder leer los valores de los píxeles de la textura 37 let canvas = document.createElement('canvas'); 38 let ctx = canvas.getContext('2d'); 39 let img = texture.image; 40 41 // Ajustamos el tamaño del canvas segun la cantidad de segmentos horizontales y verticales 42 canvas.width = widthSegments; 43 canvas.height = heightSegments; 44 45 // Dibujamos la textura en el canvas en la escala definida por widthSegments y heightSegments 46 ctx.drawImage(img, 0, 0, widthSegments, heightSegments); 47 48 // Obtenemos los valores de los píxeles de la textura 49 let imageData = ctx.getImageData(0, 0, widthSegments, heightSegments); 50 let data = imageData.data; // Este es un array con los valores de los píxeles 51 52 const quadsPerRow = widthSegments - 1; 53 54 // Recorremos los segmentos horizontales y verticales 55 for (let i = 0; i < widthSegments - 1; i++) { 56 for (let j = 0; j < heightSegments - 1; j++) { 57 // Obtenemos los valores de los píxeles de los puntos adyacentes 58 let xPrev = undefined; 59 let xNext = undefined; 60 let yPrev = undefined; 61 let yNext = undefined; 62 63 // Obtenemos el valor del pixel en la posicion i, j 64 // console.log('getting elevation map value at: (' + i + ',' + j + ')'); 65 let z0 = data[(i + j * widthSegments) * 4] / 255; 66 67 // Obtenemos los valores de los píxeles adyacentes 68 xPrev = i > 0 ? data[(i - 1 + j * widthSegments) * 4] / 255 : undefined; 69 xNext = i < widthSegments - 1 ? (xNext = data[(i + 1 + j * widthSegments) * 4] / 255) : undefined; 70 71 yPrev = j > 0 ? data[(i + (j - 1) * widthSegments) * 4] / 255 : undefined; 72 yNext = j < heightSegments - 1 ? data[(i + (j + 1) * widthSegments) * 4] / 255 : undefined; 73 74 // calculamos la diferencia entre los valores de los píxeles adyacentes 75 // en el eje `x` y en el eje `y` de la imagen (en el espacio de la textura 76 // Ojo no confundir con el espacio 3D del modelo 3D donde Y es la altura) 77 let deltaX; 78 if (xPrev == undefined) { 79 deltaX = xNext - z0; 80 } else if (yNext == undefined) { 81 deltaX = xPrev - z0; 82 } else { 83 deltaX = (xNext - xPrev) / 2; 84 } 85 86 let deltaY; 87 if (yPrev == undefined) { 88 deltaY = yNext - z0; 89 } else if (yNext == undefined) { 90 deltaY = yPrev - z0; 91 } else { 92 deltaY = (yNext - yPrev) / 2; 93 } 94 95 // Calculamos la altura del punto en el espacio 3D 96 const z = amplitude * z0; 97 98 // Añadimos los valores de los puntos al array de posiciones 99 positions.push((width * i) / widthSegments - width / 2); 100 positions.push(z); 101 positions.push((height * j) / heightSegments - height / 2); 102 103 // Calculamos los vectores tangentes a la superficie en el ejex y en el eje y 104 let tanX = new THREE.Vector3(width / widthSegments, deltaX * amplitude, 0).normalize(); 105 let tanY = new THREE.Vector3(0, deltaY * amplitude, height / heightSegments).normalize(); 106 107 // Calculamos el vector normal a la superficie 108 let n = new THREE.Vector3(); 109 n.crossVectors(tanY, tanX); 110 111 // Añadimos los valores de los vectores normales al array de normales 112 normals.push(n.x); 113 normals.push(n.y); 114 normals.push(n.z); 115 116 uvs.push(i / (widthSegments - 1)); 117 uvs.push(j / (heightSegments - 1)); 118 119 if (i == widthSegments - 2 || j == heightSegments - 2) continue; 120 121 // Ensamblamos los triangulos 122 indices.push(i + j * quadsPerRow); 123 indices.push(i + 1 + j * quadsPerRow); 124 indices.push(i + 1 + (j + 1) * quadsPerRow); 125 126 indices.push(i + j * quadsPerRow); 127 indices.push(i + 1 + (j + 1) * quadsPerRow); 128 indices.push(i + (j + 1) * quadsPerRow); 129 } 130 } 131 132 geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); 133 geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3)); 134 geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2)); 135 geometry.setIndex(indices); 136 137 return geometry; 138 } 139 140 function onTextureLoaded(key, texture) { 141 texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 142 textures[key].object = texture; 143 console.log('Texture `' + key + '` loaded'); 144 } 145 146 function loadTextures(callback) { 147 const loadingManager = new THREE.LoadingManager(); 148 149 loadingManager.onLoad = () => { 150 console.log('All textures loaded'); 151 callback(); 152 }; 153 154 for (const key in textures) { 155 console.log("Loading textures"); 156 const loader = new THREE.TextureLoader(loadingManager); 157 const texture = textures[key]; 158 texture.object = loader.load( 159 texture.url, 160 onTextureLoaded.bind(this, key), 161 null, 162 (error) => { 163 console.error(error); 164 } 165 ); 166 } 167 } 168 169 function main() { 170 } 171 172 loadTextures(main);