1 import * as THREE from 'three'; 2 import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js'; 3 4 import tierraSecaUrl from './assets/tierra_seca.jpg' 5 import ladrillosUrl from './assets/pared_de_ladrillo.jpg' 6 7 const textures = { 8 tierra: { url: tierraSecaUrl, object: null }, 9 ladrillos: { url: ladrillosUrl, object: null }, 10 }; 11 12 function onTextureLoaded(key, texture) { 13 texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 14 textures[key].object = texture; 15 console.log('Texture `' + key + '` loaded'); 16 } 17 18 function loadTextures(callback) { 19 const loadingManager = new THREE.LoadingManager(); 20 21 loadingManager.onLoad = () => { 22 console.log('All textures loaded'); 23 callback(); 24 }; 25 26 for (const key in textures) { 27 console.log("Loading textures"); 28 const loader = new THREE.TextureLoader(loadingManager); 29 const texture = textures[key]; 30 texture.object = loader.load( 31 texture.url, 32 onTextureLoaded.bind(this, key), 33 null, 34 (error) => { 35 console.error(error); 36 } 37 ); 38 } 39 } 40 41 // const arcRadius = 3; 42 // const arcCount = 1; 43 // const columnHeight = 0; 44 // const columnWidth = 1.00; 45 // const arcWidth = arcRadius*2; 46 // const startPadding = 12; 47 // const endPadding = startPadding; 48 // const bridgeHeight = columnHeight+arcRadius+topPadding; 49 50 const topPadding = 0.25; 51 const bridgeWallThickness = 2.5; 52 const bridgeWidth = 10; 53 const roadwayHeight = 0.65; 54 55 function generateBridgeWallGeometry( 56 arcCount=2, arcRadius=3, columnWidth=1, columnHeight=0, padding=10) { 57 58 const arcWidth = arcRadius*2; 59 const startPadding = padding; 60 const endPadding = padding; 61 const bridgeLen = arcCount*(columnWidth+arcWidth)+columnWidth+startPadding+endPadding; 62 const bridgeHeight = columnHeight+arcRadius+topPadding; 63 const path = new THREE.Path(); 64 65 // generate the arcs 66 for(let i = 1; i <= arcCount; ++i) { 67 path.lineTo(startPadding+i*columnWidth+((i-1)*arcWidth), 0); 68 path.moveTo(startPadding+i*columnWidth+((i-1)*arcWidth), 0); 69 path.lineTo(startPadding+i*columnWidth+((i-1)*arcWidth), columnHeight); 70 path.arc(arcRadius, 0, arcRadius, Math.PI, 0, true) 71 path.moveTo(startPadding+i*(columnWidth+arcWidth), 0); 72 path.lineTo(startPadding+i*(columnWidth+arcWidth), 0); 73 } 74 75 // no we close the curve 76 path.lineTo(bridgeLen, 0); 77 path.lineTo(bridgeLen, bridgeHeight); 78 79 path.lineTo(0, bridgeHeight); 80 path.lineTo(0, 0); 81 82 /* 83 // muestra la curva utilizada para la extrusión 84 const geometry = new THREE.BufferGeometry().setFromPoints(points); 85 const lineMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 }); 86 const curveObject = new THREE.Line(geometry, lineMaterial); 87 scene.add(curveObject); 88 */ 89 90 const points = path.getPoints(); 91 const shape = new THREE.Shape(points); 92 93 const extrudeSettings = { 94 curveSegments: 24, 95 steps: 50, 96 depth: bridgeWallThickness, 97 bevelEnabled: false 98 }; 99 100 const bridgeWallGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); 101 bridgeWallGeometry.translate(-bridgeLen/2, 0, -bridgeWallThickness/2); 102 return bridgeWallGeometry; 103 } 104 105 function generateBridgeCage(squaresCount=3, squareTubeRadius=0.15) { 106 const squareLen = bridgeWidth - 0.25; 107 const bridgeCageLen = squaresCount * squareLen; 108 109 let geometries = [] 110 111 let cylinderBase, cylinderCorner, cylinderCrossbar; 112 for(let square = 0; square < squaresCount; ++square) { 113 // 0 -> 00 114 // 1 -> 01 115 // 2 -> 10 116 // 3 -> 11 117 for(let i = 0; i < 4; ++i) { 118 cylinderBase = new THREE.CylinderGeometry( 119 squareTubeRadius, squareTubeRadius, squareLen); 120 121 cylinderCorner = cylinderBase.clone(); 122 123 const squareHypotenuse = Math.sqrt(2*squareLen*squareLen); 124 cylinderCrossbar = new THREE.CylinderGeometry( 125 squareTubeRadius, squareTubeRadius, squareHypotenuse); 126 127 if((i % 2) == 0) { 128 cylinderBase.rotateZ(Math.PI/2); 129 cylinderBase.translate( 130 0, 131 square*(squareLen), 132 ((-1)**(i>>1))*squareLen/2); 133 134 cylinderCrossbar.rotateZ((-1)**((i>>1))*Math.PI/4); 135 cylinderCrossbar.translate( 136 0, 137 square*(squareLen)+(squareLen/2), 138 ((-1)**(i>>1))*squareLen/2); 139 140 cylinderCorner.translate( 141 ((-1)**(i>>1))*squareLen/2, 142 square*(squareLen)+(squareLen/2), 143 ((-1)**(i&1))*squareLen/2); 144 } else { 145 cylinderBase.rotateX(Math.PI/2); 146 cylinderBase.translate( 147 ((-1)**(i>>1))*squareLen/2, 148 square*(squareLen), 149 0); 150 151 cylinderCrossbar.rotateX((-1)**((i>>1))*Math.PI/4); 152 cylinderCrossbar.translate( 153 ((-1)**(i>>1))*squareLen/2, 154 square*(squareLen)+(squareLen/2), 155 0); 156 157 cylinderCorner.translate( 158 ((-1)**(i>>1))*squareLen/2, 159 square*(squareLen)+(squareLen/2), 160 ((-1)**(i&1))*squareLen/2); 161 } 162 geometries.push(cylinderBase); 163 geometries.push(cylinderCrossbar); 164 geometries.push(cylinderCorner); 165 } 166 167 // agregamos un cuadrado mas para 'cerrar' la 'jaula' 168 if((square + 1) == squaresCount) { 169 for(let i = 0; i < 4; ++i) { 170 cylinderBase = new THREE.CylinderGeometry( 171 squareTubeRadius, squareTubeRadius, squareLen); 172 173 if((i % 2) == 0) { 174 cylinderBase.rotateZ(Math.PI/2); 175 cylinderBase.translate( 176 0, 177 (square+1)*(squareLen), 178 ((-1)**(i>>1))*squareLen/2); 179 } else { 180 cylinderBase.rotateX(Math.PI/2); 181 cylinderBase.translate( 182 ((-1)**(i>>1))*squareLen/2, 183 (square+1)*(squareLen), 0); 184 } 185 geometries.push(cylinderBase); 186 } 187 } 188 } 189 190 const bridgeCage = mergeGeometries(geometries); 191 bridgeCage.rotateZ(Math.PI/2); 192 bridgeCage.translate(bridgeCageLen/2, squareLen/2, 0); 193 return bridgeCage; 194 } 195 196 export function generateBridge(arcCount=1, arcRadius=3, 197 columnWidth=0, columnHeight=0, padding=10, squaresCount=0, squareLen=1) { 198 199 const arcWidth = arcRadius*2; 200 const startPadding = padding; 201 const endPadding = padding; 202 const bridgeHeight = columnHeight+arcRadius+topPadding; 203 const bridgeLen = arcCount*(columnWidth+arcWidth)+columnWidth+startPadding+endPadding; 204 const squareTubeRadius = 0.30; 205 206 const bridge = new THREE.Object3D(); 207 208 const leftWallGeometry = generateBridgeWallGeometry( 209 arcCount, arcRadius, columnWidth, columnHeight, padding); 210 211 // const leftWallGeometry = generateBridgeWallGeometry(); 212 leftWallGeometry.translate(0, 0, -bridgeWidth/2); 213 214 const rightWallGeometry = generateBridgeWallGeometry( 215 arcCount, arcRadius, columnWidth, columnHeight, padding); 216 217 // const rightWallGeometry = generateBridgeWallGeometry(); 218 rightWallGeometry.translate(0, 0, bridgeWidth/2) 219 220 const bridgeColumnsGeometry = mergeGeometries([leftWallGeometry, rightWallGeometry]); 221 222 const bridgeRoadwayGeometry = new THREE.BoxGeometry( 223 bridgeLen, roadwayHeight, bridgeWidth+bridgeWallThickness, 224 ); 225 226 bridgeRoadwayGeometry.translate(0, bridgeHeight+roadwayHeight/2, 0); 227 228 textures.ladrillos.object.wrapS = THREE.RepeatWrapping; 229 textures.ladrillos.object.wrapT = THREE.RepeatWrapping; 230 textures.ladrillos.object.repeat.set(0.75*0.15, 0.75*0.35); 231 // textures.ladrillos.object.anisotropy = 16; 232 233 const bridgeMaterial = new THREE.MeshPhongMaterial({ 234 side: THREE.FrontSide, 235 map: textures.ladrillos.object 236 }); 237 238 /* 239 textures.ladrillos2.object.wrapS = THREE.RepeatWrapping; 240 textures.ladrillos2.object.wrapT = THREE.RepeatWrapping; 241 textures.ladrillos2.object.repeat.set(0.75*5, 0.75*0.75); 242 textures.ladrillos2.object.anisotropy = 16; 243 244 const roadwayMaterial = new THREE.MeshPhongMaterial({ 245 side: THREE.DoubleSide, 246 transparent: false, 247 opacity: 1.0, 248 shininess: 10, 249 map: textures.ladrillos2.object 250 // color: 0xFF0000 251 }); 252 253 const bridgeRoadway = new THREE.Mesh(bridgeRoadwayGeometry, roadwayMaterial); 254 scene.add(bridgeRoadway); 255 */ 256 257 const bridgeColumns = new THREE.Mesh(bridgeColumnsGeometry, bridgeMaterial); 258 bridgeColumns.castShadow = true; 259 bridgeColumns.receiveShadow = true; 260 261 bridge.add(bridgeColumns); 262 263 // para reutilizar la textura de ladrillos usada en los arcos se escalan las 264 // coordenadas uv de la geometria de la parte superior 265 let uvs = bridgeRoadwayGeometry.attributes.uv.array; 266 for (let i = 0, len = uvs.length; i < len; i++) { 267 uvs[i] = (i % 2) ? uvs[i]*2.50 : uvs[i]*30.0; 268 } 269 270 const bridgeRoadway = new THREE.Mesh(bridgeRoadwayGeometry, bridgeMaterial); 271 bridge.add(bridgeRoadway); 272 273 bridgeRoadway.castShadow = true; 274 bridgeRoadway.receiveShadow = true; 275 276 const cageGeometry = generateBridgeCage(squaresCount) 277 cageGeometry.translate(0, bridgeHeight+roadwayHeight-squareTubeRadius*2, 0); 278 279 const cageMaterial = new THREE.MeshPhongMaterial({ 280 side: THREE.FrontSide, 281 transparent: false, 282 opacity: 1.0, 283 shininess: 10, 284 color: 0xFFFFFF 285 }); 286 287 const bridgeCage = new THREE.Mesh(cageGeometry, cageMaterial); 288 289 bridgeCage.castShadow = true; 290 bridgeCage.receiveShadow = true; 291 292 bridge.add(bridgeCage); 293 294 const roadwayFloorGeometry = new THREE.BoxGeometry( 295 bridgeWidth+bridgeWallThickness+0.01, 296 bridgeLen+0.01, 0.10); 297 298 roadwayFloorGeometry.rotateZ(Math.PI/2) 299 roadwayFloorGeometry.rotateX(Math.PI/2) 300 roadwayFloorGeometry.translate(0, bridgeHeight+roadwayHeight, 0) 301 302 textures.tierra.object.wrapS = THREE.MirroredRepeatWrapping; 303 textures.tierra.object.wrapT = THREE.MirroredRepeatWrapping; 304 textures.tierra.object.repeat.set(1, 5); 305 textures.tierra.object.anisotropy = 16; 306 307 const roadwayFloorMaterial = new THREE.MeshPhongMaterial({ 308 side: THREE.FrontSide, 309 map: textures.tierra.object 310 }); 311 312 const roadwayFloor = new THREE.Mesh(roadwayFloorGeometry, roadwayFloorMaterial); 313 roadwayFloor.receiveShadow = true; 314 roadwayFloor.castShadow = false; 315 316 bridge.add(roadwayFloor) 317 return bridge; 318 } 319 320 function main() { 321 } 322 323 loadTextures(main);