1 import * as THREE from 'three'; 2 import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js'; 3 4 const steamChamberLen = 10; 5 const steamChamberRad = 2.50; 6 const steamChamberEndRad = steamChamberRad+0.375; 7 const steamChamberEndLen = 2.50; 8 const cabinLen = 5; 9 const cabinHeight = 6; 10 const cabinRoofHeight = 3; 11 const cabinWallThickness = 0.375; 12 const wheelRad = 1.475; 13 const chassisHeight = 2.5; 14 const wheelThickness = 0.425; 15 const chassisOffset = 1.245; 16 const wheelOffset = -0.45; 17 const steamCylindersLen = 4; 18 const crankLen = 12; 19 const crankOffset = 1.50; 20 const crankWidth = 0.25; 21 22 let crankLeft, crankRight; 23 24 function buildCabinRoof() { 25 console.log('Building train cabin roof'); 26 const geometry = new THREE.BoxGeometry(6, cabinWallThickness, 6); 27 return geometry; 28 } 29 30 function buildCabin() { 31 console.log('Building train cabin'); 32 33 let cabin = []; 34 35 const cabinFront = new THREE.BoxGeometry( 36 steamChamberRad*2, 37 cabinWallThickness, 38 cabinHeight); 39 40 cabinFront.translate(0, cabinLen/2, -cabinHeight/2); 41 cabin.push(cabinFront); 42 43 const cabinLeft = new THREE.BoxGeometry( 44 steamChamberRad*2, 45 cabinWallThickness, 46 cabinHeight); 47 48 cabinLeft.rotateZ(Math.PI/2); 49 cabinLeft.translate( 50 steamChamberRad-cabinWallThickness/2, 51 cabinWallThickness/2, 52 -cabinHeight/2); 53 54 cabin.push(cabinLeft); 55 56 const cabinRight = new THREE.BoxGeometry( 57 steamChamberRad*2, 58 cabinWallThickness, 59 cabinHeight); 60 61 cabinRight.rotateZ(Math.PI/2); 62 cabinRight.translate( 63 -steamChamberRad+cabinWallThickness/2, 64 cabinWallThickness/2, 65 -cabinHeight/2); 66 67 cabin.push(cabinRight); 68 69 const g1 = new THREE.BoxGeometry( 70 cabinWallThickness, cabinWallThickness, cabinRoofHeight); 71 72 g1.rotateZ(Math.PI/2); 73 g1.translate( 74 -steamChamberRad+(cabinWallThickness/2), 75 -steamChamberRad+cabinWallThickness, 76 -cabinHeight-cabinRoofHeight/2); 77 78 cabin.push(g1); 79 80 const g2 = new THREE.BoxGeometry( 81 cabinWallThickness, cabinWallThickness, cabinRoofHeight); 82 83 g2.rotateZ(Math.PI/2); 84 g2.translate( 85 steamChamberRad-cabinWallThickness/2, 86 steamChamberRad, 87 -cabinHeight-cabinRoofHeight/2); 88 89 cabin.push(g2); 90 91 const g3 = new THREE.BoxGeometry( 92 cabinWallThickness, cabinWallThickness, cabinRoofHeight); 93 94 g3.rotateZ(Math.PI/2); 95 g3.translate( 96 steamChamberRad-cabinWallThickness/2, 97 -steamChamberRad+cabinWallThickness, 98 -cabinHeight-cabinRoofHeight/2); 99 100 cabin.push(g3); 101 102 const g4 = new THREE.BoxGeometry( 103 cabinWallThickness, cabinWallThickness, cabinRoofHeight); 104 105 g4.rotateZ(Math.PI/2); 106 g4.translate( 107 -steamChamberRad+cabinWallThickness/2, 108 steamChamberRad, 109 -cabinHeight-cabinRoofHeight/2); 110 111 cabin.push(g4); 112 113 const geometry = BufferGeometryUtils.mergeGeometries(cabin); 114 geometry.rotateX(Math.PI/2); 115 return geometry; 116 } 117 118 function buildChamber() { 119 let geometries = []; 120 121 const steamChamber = new THREE.CylinderGeometry(steamChamberRad, 122 steamChamberRad, steamChamberLen, 32); 123 124 geometries.push(steamChamber); 125 126 const steamChamberEnd = new THREE.CylinderGeometry(steamChamberEndRad, 127 steamChamberEndRad, steamChamberEndLen, 32); 128 129 steamChamberEnd.translate(0,steamChamberLen/2 + steamChamberEndLen/2,0); 130 geometries.push(steamChamberEnd); 131 132 const floor = new THREE.BoxGeometry( 133 steamChamberRad*2, steamChamberLen + steamChamberEndLen + cabinLen, 1.0); 134 floor.translate(0, -steamChamberEndLen/2, steamChamberRad); 135 geometries.push(floor); 136 137 const chamberPipeLen = 4; 138 const chamberPipe = new THREE.CylinderGeometry(0.55, 0.55, chamberPipeLen, 32); 139 chamberPipe.translate(0, -(steamChamberRad + chamberPipeLen/2)+1.0, 140 -(steamChamberLen+steamChamberEndLen)/2); 141 142 chamberPipe.rotateX(Math.PI/2); 143 geometries.push(chamberPipe); 144 145 const geometry = BufferGeometryUtils.mergeGeometries(geometries); 146 geometry.rotateX(Math.PI/2); 147 geometry.translate(0, steamChamberRad+0.25, 0); 148 return geometry; 149 } 150 151 function buildTrainWheel() { 152 const wheelGeometry = new THREE.CylinderGeometry(wheelRad, wheelRad, wheelThickness); 153 wheelGeometry.rotateZ(Math.PI/2); 154 155 const wheelBolt = new THREE.CylinderGeometry(wheelRad, wheelRad, wheelThickness); 156 wheelBolt.rotateZ(Math.PI/2); 157 158 const wheelsMaterial = new THREE.MeshPhongMaterial({ 159 color: 0x393939, 160 side: THREE.FrontSide, 161 shininess: 100.0 162 }); 163 164 const wheel = new THREE.Mesh(wheelGeometry, wheelsMaterial); 165 wheel.castShadow = true; 166 wheel.receiveShadow = true; 167 return wheel; 168 } 169 170 function buildTrainAxe(material) { 171 const axeGeometry = new THREE.CylinderGeometry(0.325, 0.325, 5); 172 axeGeometry.rotateZ(Math.PI/2); 173 174 const axeMaterial = new THREE.MeshPhongMaterial({ 175 color: 0x7A7F80, 176 side: THREE.FrontSide, 177 shininess: 100.0 178 }); 179 180 return new THREE.Mesh(axeGeometry, axeMaterial); 181 } 182 183 function buildTrainChassis() { 184 const chassis = new THREE.BoxGeometry(3.5, 2.5, steamChamberLen+steamChamberEndLen+cabinLen); 185 return chassis; 186 } 187 188 export function buildTrain() { 189 console.log('Building train'); 190 const train = new THREE.Object3D(); 191 192 const chassisGeometry = buildTrainChassis(); 193 const chassisMaterial = new THREE.MeshPhongMaterial({ 194 color: 0x7A7F80, 195 side: THREE.FrontSide, 196 shininess: 100.0 197 }); 198 199 const chassis = new THREE.Mesh(chassisGeometry, chassisMaterial); 200 chassis.castShadow = true; 201 chassis.receiveShadow = true; 202 train.add(chassis); 203 204 const chamberGeometry = buildChamber(); 205 const chamberMaterial = new THREE.MeshPhongMaterial({ 206 color: 0xFA1A09, 207 side: THREE.FrontSide, 208 shininess: 100.0 209 }); 210 211 const chamber = new THREE.Mesh(chamberGeometry, chamberMaterial); 212 chamber.castShadow = true; 213 chamber.receive = true; 214 chassis.add(chamber); 215 chamber.position.set(0, (chassisHeight + cabinWallThickness)/2, chassisOffset); 216 217 const cabinGeometry = buildCabin(); 218 const cabin = new THREE.Mesh(cabinGeometry, chamberMaterial); 219 cabin.castShadow = true; 220 cabin.receive = true; 221 chassis.add(cabin); 222 cabin.position.set(0, 223 (chassisHeight + cabinWallThickness)/2, 224 -steamChamberLen+(cabinLen/2)+chassisOffset); 225 226 const cabinRoofGeometry = buildCabinRoof(); 227 const roofMaterial = new THREE.MeshPhongMaterial({ 228 color: 0xFBEC50, 229 side: THREE.FrontSide, 230 shininess: 100.0 231 }); 232 233 const cabinRoof = new THREE.Mesh(cabinRoofGeometry, roofMaterial); 234 cabinRoof.castShadow = true; 235 cabinRoof.receive = true; 236 cabin.add(cabinRoof); 237 cabinRoof.position.set(0, cabinHeight+cabinRoofHeight+cabinWallThickness/2, 0); 238 239 const a1 = buildTrainAxe(); 240 chassis.add(a1); 241 242 const a2 = buildTrainAxe(); 243 chassis.add(a2); 244 245 const a3 = buildTrainAxe(chassisMaterial); 246 chassis.add(a3); 247 248 a1.position.set(0, wheelOffset, -0.60); 249 a2.position.set(0, wheelOffset, -0.60+wheelRad*2.5); 250 a3.position.set(0, wheelOffset, -0.60-wheelRad*2.5); 251 252 const cylinderLeft = new THREE.CylinderGeometry(1.25, 1.5, steamCylindersLen); 253 cylinderLeft.rotateX(Math.PI/2); 254 cylinderLeft.translate(steamChamberRad-0.25, -.25, steamChamberLen-steamCylindersLen/1.5); 255 256 const cylinderRight = new THREE.CylinderGeometry(1.25, 1.5, steamCylindersLen); 257 cylinderRight.rotateX(Math.PI/2); 258 cylinderRight.translate(-steamChamberRad+0.25, -.25, steamChamberLen-steamCylindersLen/1.5); 259 260 const cylindersGeometry = BufferGeometryUtils.mergeGeometries([cylinderRight, cylinderLeft]); 261 const cylindersMaterial = new THREE.MeshPhongMaterial({ 262 color: 0x393939, 263 side: THREE.FrontSide, 264 shininess: 100.0 265 }); 266 267 const cylinders = new THREE.Mesh(cylindersGeometry, cylindersMaterial) 268 cylinders.castShadow = true; 269 cylinders.receiveShadow = true; 270 chassis.add(cylinders); 271 chassis.position.set(0,-2,-2.75); 272 273 const w1 = buildTrainWheel(); 274 w1.position.set(steamChamberRad-wheelThickness/2.1,0,0); 275 a1.add(w1); 276 277 const w2 = buildTrainWheel(); 278 w2.position.set(-steamChamberRad+wheelThickness/2.1,0,0); 279 a1.add(w2); 280 281 const w3 = buildTrainWheel(); 282 w3.position.set(steamChamberRad-wheelThickness/2.1,0,0); 283 a2.add(w3); 284 285 const w4 = buildTrainWheel(); 286 w4.position.set(-steamChamberRad+wheelThickness/2.1,0,0); 287 a2.add(w4); 288 289 const w5 = buildTrainWheel(); 290 w5.position.set(steamChamberRad-wheelThickness/2.1,0,0); 291 a3.add(w5); 292 293 const w6 = buildTrainWheel(); 294 w6.position.set(-steamChamberRad+wheelThickness/2.1,0,0); 295 a3.add(w6); 296 297 const crankGeometry = new THREE.BoxGeometry(crankWidth, 0.5, crankLen); 298 299 crankRight = new THREE.Mesh(crankGeometry, chassisMaterial); 300 //crankRight.position.set(steamChamberRad, wheelOffset, crankOffset); 301 302 crankLeft = new THREE.Mesh(crankGeometry, chassisMaterial); 303 //crankLeft.position.set(-steamChamberRad, wheelOffset, crankOffset); 304 305 chassis.add(crankLeft); 306 chassis.add(crankRight); 307 308 // chassis.translateY(-wheelOffset); 309 updateTrainCrankPosition(); 310 311 const lightRad = 1.10; 312 const lightGeometry = new THREE.CylinderGeometry(lightRad, lightRad, 1, 32); 313 lightGeometry.rotateX(Math.PI/2); 314 315 const lightMaterial = new THREE.MeshPhongMaterial({ 316 color: 0x393939, 317 side: THREE.FrontSide, 318 shininess: 100.0, 319 emissive: 0xf6d32d 320 }); 321 322 const trainLightLamp = new THREE.Mesh(lightGeometry, lightMaterial); 323 train.add(trainLightLamp) 324 trainLightLamp.position.set(0, 325 steamChamberRad+chassisOffset-lightRad/2-.30, 326 (steamChamberLen+steamChamberEndLen)/2-.3); 327 328 train.position.set(0, 2, 0); 329 return train; 330 } 331 332 export function updateTrainCrankPosition(time = 0.0) { 333 crankLeft.position.set(-steamChamberRad-crankWidth/2, 334 wheelOffset + 0.70*(Math.sin(time*Math.PI/2)), 335 crankOffset - 0.70*(Math.cos(time*Math.PI/2))); 336 337 crankRight.position.set(steamChamberRad+crankWidth/2, 338 wheelOffset + 0.70*(Math.sin(time*Math.PI/2)), 339 crankOffset - 0.70*(Math.cos(time*Math.PI/2))); 340 }