1 import * as THREE from 'three'; 2 3 let treesForbiddenMapData, treesForbiddenMap, elevationMap, elevationMapData; 4 5 import elevationMapUrl from './assets/elevation_map_wider_river.png' 6 import treeForbiddenMapUrl from './assets/tree_forbidden_zone_map_wider_path.png' 7 8 const textures = { 9 elevationMap: { url: elevationMapUrl, object: null }, 10 treeForbiddenMap: { url: treeForbiddenMapUrl, object: null } 11 }; 12 13 const widthSegments = 100; 14 const heightSegments = 100; 15 const amplitude = 10; 16 const amplitudeBottom = -1.00; 17 const imgWidth = 512; 18 const imgHeight = 512; 19 20 function getPixel(imgData, index) { 21 let i = index*4, d = imgData.data 22 return [d[i],d[i+1],d[i+2],d[i+3]] // Returns array [R,G,B,A] 23 } 24 25 function getPixelXY(imgData, x, y) { 26 return getPixel(imgData, y*imgData.width+x) 27 } 28 29 // position: Vector3 30 function isForbbidenPosition(position) { 31 const x = Math.floor(position.x); 32 const y = position.y; 33 const z = Math.floor(position.z); 34 35 // TODO: estos valores deberian depender de la posicion del terreno 36 if((y > 6.8) || (y < 3.25)) { 37 // console.log("(" + position.x + ", " + position.y + ", " + position.z + ") is not valid "); 38 return true; 39 } 40 41 let pixelArray = getPixelXY(treesForbiddenMap, x, z); 42 const R = pixelArray[0]; // Red 43 const G = pixelArray[1]; // Green 44 const B = pixelArray[2]; // Blue 45 const A = pixelArray[3]; // Alpha 46 // const pixel = new THREE.Vector4(R, G, B, A); 47 48 if(((R <= 10) && (G >= 250) && (B <= 10)) 49 || (R <= 80) && (G <= 80) && (B <= 80) 50 || (R >= 200) && (G >= 200) && (B >= 200)) { 51 // console.log("(" + position.x + ", " + position.y + ", " + position.z + ") is not valid "); 52 return true; 53 } 54 55 // console.log("(" + position.x + ", " + position.y + ") is valid "); 56 return false; 57 } 58 59 // obtiene una posicion aleatoria en el terreno, para obtener la altura del 60 // terreno utiliza el mapa de elevacion. 61 // `padding` permite definir un borde del cual no se toman puntos 62 function getRandomPositionInTerrain(padding = 0) { 63 const x = Math.floor(Math.random() * (widthSegments-(padding*2))); 64 const z = Math.floor(Math.random() * (heightSegments-(padding*2))); 65 66 const pixelArray = getPixelXY(elevationMap, x, z); // array [R,G,B,A] 67 const y = (pixelArray[0]/255)*amplitude; 68 69 const position = new THREE.Vector3(x+padding, y, z+padding); 70 return position; 71 } 72 73 // devuelve un arreglo de 2 `instancedMesh` con los troncos y copas de los arboles 74 export function createInstancedTrees(count) { 75 console.log('Generating `' + count + '` instances of tree'); 76 77 let logHeight = 3.0; 78 const treeLogGeometry = new THREE.CylinderGeometry( 79 0.10, 0.25, logHeight, 40, 40); 80 treeLogGeometry.translate(0, logHeight/2.0, 0); 81 const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry(); 82 instancedTreeLogGeometry.copy(treeLogGeometry); 83 const treeLogMaterial = new THREE.MeshPhongMaterial({color: 0x7c3f00, side: THREE.FrontSide}); 84 const instancedTreeLogs = new THREE.InstancedMesh( 85 instancedTreeLogGeometry, 86 treeLogMaterial, 87 count); 88 89 const treeLeavesRadius = 1.25; 90 const treeLeavesGeometry = new THREE.SphereGeometry(treeLeavesRadius,40,40); 91 const instancedTreeLeavesGeometry = new THREE.InstancedBufferGeometry(); 92 instancedTreeLeavesGeometry.copy(treeLeavesGeometry); 93 const treeLeavesMaterial = new THREE.MeshPhongMaterial({color: 0x365829}); 94 const instancedTreeLeaves = new THREE.InstancedMesh( 95 instancedTreeLeavesGeometry, 96 treeLeavesMaterial, 97 count); 98 99 const rotMatrix = new THREE.Matrix4(); 100 const translationMatrix = new THREE.Matrix4(); 101 const treeLogMatrix = new THREE.Matrix4(); 102 const treeLeavesMatrix = new THREE.Matrix4(); 103 104 const treesBorderPadding = 3.0; 105 for (let i = 0; i < count; i++) { 106 let position = getRandomPositionInTerrain(treesBorderPadding); 107 for(let j = 0; isForbbidenPosition(position); ++j) { 108 position = getRandomPositionInTerrain(treesBorderPadding); 109 if(j++ == 1000) { // maximo de iteraciones 110 break; 111 } 112 } 113 114 if(isForbbidenPosition(position)) { 115 continue; 116 } 117 118 const treeOffset = -1.50; 119 120 // 1.50 numbero magico para posicionar correctamente los arboles con 121 // respecto al terreno 122 position.x -= (widthSegments+treesBorderPadding+1.50)/2; 123 position.y += (amplitudeBottom + treeOffset); 124 position.z -= (heightSegments+treesBorderPadding)/2; 125 translationMatrix.makeTranslation(position); 126 treeLogMatrix.identity(); 127 treeLeavesMatrix.identity(); 128 129 let scale = 0.6 + (Math.random()*(logHeight/3)); 130 treeLogMatrix.makeScale(1, scale, 1); 131 treeLogMatrix.premultiply(translationMatrix); 132 133 position.y += scale*logHeight; 134 135 translationMatrix.makeTranslation(position); 136 treeLeavesMatrix.premultiply(translationMatrix); 137 138 instancedTreeLogs.setMatrixAt(i, treeLogMatrix); 139 instancedTreeLeaves.setMatrixAt(i, treeLeavesMatrix); 140 } 141 142 console.log('Done generating `' + count + '` instances of tree'); 143 return [instancedTreeLogs, instancedTreeLeaves]; 144 } 145 146 function loadMapsData() { 147 console.log("Loading maps data"); 148 149 // Creamos un canvas para poder leer los valores de los píxeles de la textura 150 let canvas = document.createElement('canvas'); 151 let ctx = canvas.getContext('2d'); 152 153 let treesForbiddenMapImage = textures.treeForbiddenMap.object.image; 154 let elevationMapImage = textures.elevationMap.object.image; 155 156 // ambos mapas deben tener el mismo tamaño 157 const imgWidth = widthSegments; 158 const imgHeight = heightSegments; 159 160 canvas.width = imgWidth; 161 canvas.height = imgHeight; 162 163 ctx.drawImage(treesForbiddenMapImage, 0, 0, imgWidth, imgHeight); 164 treesForbiddenMap = ctx.getImageData(0, 0, imgWidth, imgHeight); 165 treesForbiddenMapData = treesForbiddenMap.data; 166 167 ctx.drawImage(elevationMapImage, 0, 0, imgWidth, imgHeight); 168 elevationMap = ctx.getImageData(0, 0, imgWidth, imgHeight); 169 elevationMapData = elevationMap.data 170 171 console.log("All maps data loaded succesfully"); 172 } 173 174 function onTextureLoaded(key, texture) { 175 texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 176 textures[key].object = texture; 177 console.log('Texture `' + key + '` loaded'); 178 } 179 180 function loadTextures(callback) { 181 const loadingManager = new THREE.LoadingManager(); 182 183 loadingManager.onLoad = () => { 184 console.log('All textures loaded'); 185 callback(); 186 }; 187 188 for (const key in textures) { 189 console.log("Loading textures"); 190 const loader = new THREE.TextureLoader(loadingManager); 191 const texture = textures[key]; 192 texture.object = loader.load( 193 texture.url, 194 onTextureLoaded.bind(this, key), 195 null, 196 (error) => { 197 console.error(error); 198 } 199 ); 200 } 201 } 202 203 loadTextures(loadMapsData);