1// jbmodelgallery2.js 2let jbScene, jbCamera, jbRenderer, jbControls; 3const jbContainer = document.getElementById('jbmodel-container'); 4let jbLoadingManager; 5let jbTerrainModel; 6let jbInitialized = false; 7let jbAnimationRunning = false; 8let JB_ROTATION_SPEED = 0.0001; // Initialize rotation speed 9 10// Add this state tracking object at the top level 11const JBModelState = { 12 isLoading: false, 13 loadingManager: null, 14 resourcesLoaded: false 15}; 16 17 18function jbAnimate() { 19 if (!jbAnimationRunning) return; 20 requestAnimationFrame(jbAnimate); 21 try { 22 if (jbControls && jbControls.update) { 23 jbControls.update(); 24 } 25 // Add this rotation check: 26 if (jbTerrainModel && JB_ROTATION_SPEED !== 0) { 27 jbTerrainModel.rotation.y += JB_ROTATION_SPEED; 28 } 29 if (jbRenderer && jbScene && jbCamera) { 30 jbRenderer.render(jbScene, jbCamera); 31 } 32 } catch (error) { 33 jbLog(`Animation error: ${error.message}`, 'error'); 34 } 35} 36 37 38// Add click handler to toggle rotation 39function initRotationControl() { 40 if (jbRenderer && jbRenderer.domElement) { 41 jbRenderer.domElement.addEventListener('click', () => { 42 JB_ROTATION_SPEED = JB_ROTATION_SPEED === 0 ? 0.0001 : 0; 43 console.log('Rotation speed:', JB_ROTATION_SPEED); 44 }, false); 45 } 46} 47 48function cleanupJBModel() { 49 jbAnimationRunning = false; 50 JB_ROTATION_SPEED = 0.0001; // Reset rotation speed to initial value 51 52 // Cancel any pending loading operations 53 if (JBModelState.loadingManager) { 54 JBModelState.loadingManager.onLoad = () => {}; 55 JBModelState.loadingManager.onProgress = () => {}; 56 JBModelState.loadingManager.onError = () => {}; 57 } 58 59 // Clean up WebGL renderer 60 if (jbRenderer) { 61 // Remove click event listener 62 if (jbRenderer.domElement) { 63 jbRenderer.domElement.removeEventListener('click', null); 64 } 65 66 jbRenderer.dispose(); 67 jbRenderer.forceContextLoss(); 68 if (jbRenderer.domElement && jbRenderer.domElement.parentNode) { 69 jbRenderer.domElement.parentNode.removeChild(jbRenderer.domElement); 70 } 71 jbRenderer = null; 72 } 73 74 if (jbControls) { 75 jbControls.dispose(); 76 jbControls = null; 77 } 78 79 if (jbScene) { 80 jbScene.traverse((object) => { 81 if (object.geometry) { 82 object.geometry.dispose(); 83 } 84 if (object.material) { 85 if (Array.isArray(object.material)) { 86 object.material.forEach(material => { 87 if (material.map) material.map.dispose(); 88 material.dispose(); 89 }); 90 } else { 91 if (object.material.map) object.material.map.dispose(); 92 object.material.dispose(); 93 } 94 } 95 }); 96 jbScene = null; 97 } 98 99 jbCamera = null; 100 jbTerrainModel = null; 101 JBModelState.loadingManager = null; 102 JBModelState.resourcesLoaded = false; 103 jbInitialized = false; 104 105 window.removeEventListener('resize', jbOnWindowResize); 106 107 const container = document.getElementById('jbmodel-container'); 108 if (container) { 109 container.initialized = false; 110 while (container.firstChild) { 111 container.removeChild(container.firstChild); 112 } 113 } 114 jbLog('JB model cleaned up'); 115} 116 117 118 119 120 121function jbInit() { 122 try { 123 jbLog('Initializing jbmodel-container application...'); 124 125 // Ensure the container exists 126 const jbContainer = document.getElementById('jbmodel-container'); 127 if (!jbContainer) { 128 jbLog('jbmodel-container not found in the DOM', 'error'); 129 return; 130 } 131 132 // Scene setup 133 jbScene = new THREE.Scene(); 134 jbScene.background = new THREE.Color(0xffffff); 135 jbLog('jbScene created'); 136 137 // Camera setup
138 const jbAspectRatio = jbContainer.clientWidth / jbContainer.clientHeight; 139 const jbViewSize = 200; 140 jbCamera = new THREE.OrthographicCamera( 141 -jbViewSize * jbAspectRatio / 2, 142 jbViewSize * jbAspectRatio / 2, 143 jbViewSize / 2, 144 -jbViewSize / 2, 145 0.1, 146 1000 147 ); 148 149 jbCamera.position.set(50, 57.7, 100); 150 jbCamera.lookAt(0, 0, 0); 151 jbCamera.zoom = 1.5; 152 jbCamera.updateProjectionMatrix(); 153 jbLog(`jbCamera positioned at: ${JSON.stringify(jbCamera.position)}`); 154 155 // Renderer setup 156 jbRenderer = new THREE.WebGLRenderer({ 157 antialias: true, 158 powerPreference: "high-performance" 159 }); 160 161 jbRenderer.setSize(jbContainer.clientWidth, jbContainer.clientHeight); 162 jbRenderer.setPixelRatio(window.devicePixelRatio); 163 jbRenderer.shadowMap.enabled = true; 164 jbRenderer.shadowMap.type = THREE.PCFSoftShadowMap; 165 jbRenderer.outputEncoding = THREE.sRGBEncoding; 166 jbContainer.appendChild(jbRenderer.domElement); 167 jbLog('jbRenderer initialized'); 168 169 // Controls setup 170 jbControls = new THREE.OrbitControls(jbCamera, jbRenderer.domElement); 171 jbControls.enableDamping = true; 172 jbControls.dampingFactor = 0.05; 173 jbControls.minPolarAngle = 0; 174 jbControls.maxPolarAngle = 1.7; 175 jbControls.minDistance = 10; 176 jbControls.maxDistance = 200; 177 jbControls.minAzimuthAngle = -Infinity; 178 jbControls.maxAzimuthAngle = Infinity; 179 jbLog('jbControls initialized'); 180 181 // Initialize controls and legend 182 jbInitializeControls(); 183 184 jbSetupLoadingManager(); 185 jbSetupLights(); 186 187 // Load model and textures 188 jbLoadTerrainModel(); 189 190 window.addEventListener('resize', jbOnWindowResize, false); 191 192 // Start the animation loop 193 jbAnimationRunning = true; 194 jbAnimate(); 195 196 if (jbRenderer) { 197 jbRenderer.domElement.addEventListener('click', () => { 198 JB_ROTATION_SPEED = JB_ROTATION_SPEED === 0 ? 0.0001 : 0; 199 console.log('Rotation speed:', JB_ROTATION_SPEED); 200 }, false); 201 } 202 203 204 jbInitialized = true; 205 } catch (error) { 206 jbLog(`Initialization failed: ${error.message}`, 'error'); 207 console.error(error); 208 } 209} 210 211 212 213function jbInitializeControls() { 214 const jbControlsContainer = document.createElement('div'); 215 jbControlsContainer.id = 'jb-controls-container'; 216 217 jbControlsContainer.innerHTML = ` 218 <!-- Main Controls Group --> 219 <div class="jb-main-controls-group"> 220 <!-- Zoom & Rotate Controls --> 221 <div class="jb-controls-group"> 222 <div class="jb-group-label">Zoom & Rotate</div> 223 <div class="jb-controls-grid"> 224 <button onclick="jbZoomIn()" title="Zoom In">+</button> 225 <button onclick="jbZoomOut()" title="Zoom Out">â</button> 226 <button onclick="jbRotateLeft()" title="Rotate Left">â²</button> 227 <button onclick="jbRotateRight()" title="Rotate Right">â³</button> 228 </div> 229 </div> 230 231 <!-- Pan Controls --> 232 <div class="jb-controls-group"> 233 <div class="jb-group-label">Pan</div> 234 <div class="jb-controls-grid"> 235 <button onclick="jbPanUp()" title="Pan Up">â²</button> 236 <button onclick="jbPanLeft()" title="Pan Left">â</button> 237 <button onclick="jbPanRight()" title="Pan Right">â¶</button> 238 <button onclick="jbPanDown()" title="Pan Down">â¼</button> 239 </div> 240 </div> 241 242 <!-- View Controls --> 243 <div class="jb-controls-group"> 244 <div class="jb-group-label">View</div>
245 <div class="jb-controls-grid"> 246 <button onclick="jbViewTopDown()" title="Top View">Z</button> 247 <button onclick="jbViewFront()" title="Front View">X</button> 248 <button onclick="jbViewSide()" title="Side View">Y</button> 249 <button onclick="jbResetView()" title="Reset View">ð </button> 250 </div> 251 </div> 252 253 <!-- Satellite Toggle --> 254 <div class="jb-controls-group"> 255 <div class="jb-controls-grid"> 256 <button id="jb-satelliteToggle" onclick="jbToggleSatelliteLayer()" title="Toggle Satellite">ð°ï¸</button> 257 </div> 258 </div> 259 </div> 260 261 <!-- Legend Group --> 262 <div class="jb-legend-group"> 263 <div id="jblegend-container"> 264 <h3>Elevation</h3> 265 <ul id="jblegend"> 266 <li> 267 <div class="jbcolor-box" style="background-color: #191970;"></div> 268 <span class="jblabel">-20m to -10m</span> 269 </li> 270 <li> 271 <div class="jbcolor-box" style="background-color: #1F51FF;"></div> 272 <span class="jblabel">-10m to 0m</span> 273 </li> 274 <li> 275 <div class="jbcolor-box" style="background-color: #87CEFA;"></div> 276 <span class="jblabel">0m to 2m</span> 277 </li> 278 <li> 279 <div class="jbcolor-box" style="background-color: #ADD8E6;"></div> 280 <span class="jblabel">2m to 4m</span> 281 </li> 282 <li> 283 <div class="jbcolor-box" style="background-color: #F4A460;"></div> 284 <span class="jblabel">4m to 6m</span> 285 </li> 286 <li> 287 <div class="jbcolor-box" style="background-color: #D2B48C;"></div> 288 <span class="jblabel">6m to 8m</span> 289 </li> 290 <li> 291 <div class="jbcolor-box" style="background-color: #A0522D;"></div> 292 <span class="jblabel">8m to 10m</span> 293 </li> 294 <li> 295 <div class="jbcolor-box" style="background-color: #228B22;"></div> 296 <span class="jblabel">10m to 20m</span> 297 </li> 298 <li> 299 <div class="jbcolor-box" style="background-color: #006400;"></div> 300 <span class="jblabel">20m to 30m</span> 301 </li> 302 <li> 303 <div class="jbcolor-box" style="background-color: #8B4513;"></div> 304 <span class="jblabel">30m to 40m</span> 305 </li> 306 <li> 307 <div class="jbcolor-box" style="background-color: #B22222;"></div> 308 <span class="jblabel">40m to 50m</span> 309 </li> 310 <li> 311 <div class="jbcolor-box" style="background-color: #800080;"></div> 312 <span class="jblabel">50m to 60m</span> 313 </li> 314 <li> 315 <div class="jbcolor-box" style="background-color: #4B0082;"></div> 316 <span class="jblabel">60m+</span> 317 </li> 318 </ul> 319 </div> 320 </div> 321 `; 322 323 // Add controls to container 324 const jbModelContainer = document.getElementById('jbmodel-container'); 325 if (jbModelContainer) { 326 jbModelContainer.appendChild(jbControlsContainer); 327 jbLog('Controls and legend initialized'); 328 } else { 329 jbLog('Failed to find jbmodel-container', 'error'); 330 } 331} 332 333 334 335 336// Define step size for panning 337const jbStepSize = 10; 338 339// Pan Controls 340window.jbPanLeft = function() { 341 const jbDirection = new THREE.Vector3(1, 0, 0); 342 jbDirection.applyQuaternion(jbCamera.quaternion); 343 jbDirection.normalize(); 344 jbDirection.multiplyScalar(jbStepSize); 345 346 jbCamera.position.add(jbDirection); 347 jbControls.target.add(jbDirection); 348 jbControls.update(); 349}; 350 351window.jbPanRight = function() { 352 const jbDirection = new THREE.Vector3(-1, 0, 0); 353 jbDirection.applyQuaternion(jbCamera.quaternion); 354 jbDirection.normalize(); 355 jbDirection.multiplyScalar(jbStepSize); 356 357 jbCamera.position.add(jbDirection); 358 jbControls.target.add(jbDirection); 359 jbControls.update(); 360}; 361 362window.jbPanUp = function() { 363 const jbDirection = new THREE.Vector3(0, -1, 0); 364 jbDirection.applyQuaternion(jbCamera.quaternion); 365 jbDirection.normalize(); 366 jbDirection.multiplyScalar(jbStepSize); 367 368 jbCamera.position.add(jbDirection); 369 jbControls.target.add(jbDirection); 370 jbControls.update(); 371}; 372 373window.jbPanDown = function() { 374 const jbDirection = new THREE.Vector3(0, 1, 0); 375 jbDirection.applyQuaternion(jbCamera.quaternion); 376 jbDirection.normalize(); 377 jbDirection.multiplyScalar(jbStepSize); 378 379 jbCamera.position.add(jbDirection); 380 jbControls.target.add(jbDirection); 381 jbControls.update(); 382}; 383 384// Zoom Controls 385window.jbZoomIn = function() { 386 jbCamera.zoom *= 1.2; 387 jbCamera.updateProjectionMatrix(); 388}; 389 390window.jbZoomOut = function() { 391 jbCamera.zoom *= 0.8; 392 jbCamera.updateProjectionMatrix(); 393}; 394 395// View Controls 396window.jbViewTopDown = function() { 397 const jbDistance = jbCamera.position.distanceTo(jbControls.target);
398 jbCamera.position.set(jbControls.target.x, jbControls.target.y + jbDistance, jbControls.target.z); 399 jbCamera.lookAt(jbControls.target); 400 jbControls.update(); 401}; 402 403window.jbViewFront = function() { 404 const jbDistance = jbCamera.position.distanceTo(jbControls.target); 405 jbCamera.position.set(jbControls.target.x, jbControls.target.y, jbControls.target.z + jbDistance); 406 jbCamera.lookAt(jbControls.target); 407 jbControls.update(); 408}; 409 410window.jbViewSide = function() { 411 const jbDistance = jbCamera.position.distanceTo(jbControls.target); 412 jbCamera.position.set(jbControls.target.x + jbDistance, jbControls.target.y, jbControls.target.z); 413 jbCamera.lookAt(jbControls.target); 414 jbControls.update(); 415}; 416 417// Rotation Controls 418window.jbRotateLeft = function() { 419 const jbRotationMatrix = new THREE.Matrix4(); 420 jbRotationMatrix.makeRotationY(0.1); 421 422 const jbCameraPosition = jbCamera.position.clone().sub(jbControls.target); 423 jbCameraPosition.applyMatrix4(jbRotationMatrix); 424 jbCamera.position.copy(jbCameraPosition.add(jbControls.target)); 425 426 jbCamera.lookAt(jbControls.target); 427 jbControls.update(); 428}; 429 430window.jbRotateRight = function() { 431 const jbRotationMatrix = new THREE.Matrix4(); 432 jbRotationMatrix.makeRotationY(-0.1); 433 434 const jbCameraPosition = jbCamera.position.clone().sub(jbControls.target); 435 jbCameraPosition.applyMatrix4(jbRotationMatrix); 436 jbCamera.position.copy(jbCameraPosition.add(jbControls.target)); 437 438 jbCamera.lookAt(jbControls.target); 439 jbControls.update(); 440}; 441 442// Reset View 443window.jbResetView = function() { 444 jbControls.reset(); 445 jbCamera.zoom = 2; 446 jbCamera.updateProjectionMatrix(); 447}; 448 449 450 451function jbSetupLoadingManager() { 452 const wrapper = document.querySelector('.main-image-wrapper'); 453 if (!wrapper) return; // Exit if wrapper not found 454 455 // First remove any existing loading elements 456 const existingOverlay = document.getElementById('jbloading-overlay'); 457 if (existingOverlay) { 458 existingOverlay.remove(); 459 } 460 461 // Create new loading elements 462 const jbLoadingOverlay = document.createElement('div'); 463 jbLoadingOverlay.id = 'jbloading-overlay'; 464 jbLoadingOverlay.style.cssText = ` 465 position: absolute; 466 top: 0; 467 left: 0; 468 width: 100%; 469 height: 100%; 470 background-color: rgba(255, 255, 255, 0.8); 471 display: flex; 472 flex-direction: column; 473 justify-content: center; 474 align-items: center; 475 z-index: 999; 476 `; 477 478 const loadingMessage = document.createElement('div'); 479 loadingMessage.id = 'jbloading-message'; 480 loadingMessage.textContent = 'Creating Jamaica Bay...'; 481 loadingMessage.style.cssText = ` 482 font-size: 12px; 483 font-family: Arial, sans-serif; 484 font-weight: normal; 485 color: #333; 486 margin-bottom: 15px; 487 `; 488 489 const progressContainer = document.createElement('div'); 490 progressContainer.id = 'jbloading-progress'; 491 progressContainer.style.cssText = ` 492 width: 200px; 493 height: 10px; 494 background-color: #f0f0f0; 495 border-radius: 5px; 496 overflow: hidden; 497 `; 498 499 const jbLoadingBar = document.createElement('div'); 500 jbLoadingBar.id = 'jbloading-bar'; 501 jbLoadingBar.style.cssText = ` 502 width: 0; 503 height: 100%; 504 background-color: #005892; 505 transition: width 0.5s ease; 506 `; 507 508 // Append elements 509 progressContainer.appendChild(jbLoadingBar); 510 jbLoadingOverlay.appendChild(loadingMessage); 511 jbLoadingOverlay.appendChild(progressContainer); 512 wrapper.appendChild(jbLoadingOverlay); // Append to main-image-wrapper 513 514 515 516 // Add to the jbmodel-container 517 const modelContainer = document.getElementById('jbmodel-container'); 518 if (modelContainer) { 519 modelContainer.style.position = 'relative'; // Ensure positioning context 520 modelContainer.appendChild(jbLoadingOverlay); 521 } 522 523 // Set up loading manager 524 jbLoadingManager = new THREE.LoadingManager( 525 // onLoad 526 () => { 527 jbLog('All resources loaded.'); 528 if (jbLoadingBar && jbLoadingOverlay) { 529 jbLoadingBar.style.width = '100%'; 530 setTimeout(() => { 531 if (jbLoadingOverlay) { 532 jbLoadingOverlay.style.display = 'none'; 533 } 534 }, 500); 535 } 536 }, 537 // onProgress 538 (url, itemsLoaded, itemsTotal) => { 539 if (jbLoadingBar && loadingMessage) { 540 const jbProgress = (itemsLoaded / itemsTotal) * 100; 541 jbLoadingBar.style.width = `${jbProgress}%`; 542 loadingMessage.textContent = `Creating Jamaica Bay... ${Math.round(jbProgress)}%`; 543 jbLog(`Loading progress: ${jbProgress.toFixed(2)}% - Loaded ${url}`); 544 } 545 }, 546 // onError 547 (url) => { 548 jbLog(`Failed to load ${url}`, 'error'); 549 if (jbLoadingOverlay) { 550 jbLoadingOverlay.style.display = 'none'; 551 } 552 } 553 ); 554 555 return jbLoadingManager; 556} 557 558 559 560function jbLoadTerrainModel() { 561 const jbMtlLoader = new THREE.MTLLoader(jbLoadingManager); 562 const jbTextureLoader = new THREE.TextureLoader(jbLoadingManager); 563 564 // Load satellite texture 565 jbTextureLoader.load('https://aurashkhawarzad.com/gallery/jbmodelgallery/jbterrain_model_satellite_texture.png', (jbSatelliteTexture) => { 566 jbLog('Satellite texture loaded'); 567 jbSatelliteTexture.encoding = THREE.sRGBEncoding; 568 569 // Load materials 570 jbMtlLoader.load('https://aurashkhawarzad.com/gallery/jbmodelgallery/jbterrain_model.mtl', (jbMaterials) => { 571 jbMaterials.preload(); 572 jbLog('Materials loaded successfully'); 573 574 const jbObjLoader = new THREE.OBJLoader(jbLoadingManager); 575 jbObjLoader.setMaterials(jbMaterials); 576 577 jbObjLoader.load('https://aurashkhawarzad.com/gallery/jbmodelgallery/jbterrain_model.obj', (jbObject) => { 578 jbTerrainModel = jbObject; 579 580 // Debug log the loaded model structure 581 jbLog('Model loaded, analyzing materials...'); 582 583 jbTerrainModel.traverse((jbChild) => { 584 if (jbChild instanceof THREE.Mesh) { 585 // Log all materials for debugging 586 jbLog(`Found mesh with material named: ${jbChild.material.name}`); 587 588 // If this mesh uses the satellite material 589 if (jbChild.material && jbChild.material.name === 'satellite_material') { 590 // Create new material with proper settings 591 jbChild.material = new THREE.MeshStandardMaterial({ 592 map: jbSatelliteTexture, 593 transparent: true, 594 opacity: 1.0, 595 name: 'satellite_material' 596 }); 597 jbLog('Applied satellite material to mesh'); 598 } 599 } 600 }); 601 602 jbScene.add(jbTerrainModel); 603 jbLog('Terrain model added to scene'); 604 605 // Set initial state 606 jbSetSatelliteLayerVisibility(false); 607 }); 608 }); 609 });
610} 611 612function jbLog(message, level = 'info') { 613 const timestamp = new Date().toISOString(); 614 const logMessage = `[${timestamp}] ${message}`; 615 616 if (level === 'error') { 617 console.error(logMessage); 618 } else { 619 console.log(logMessage); 620 } 621} 622 623 624function jbSetSatelliteLayerVisibility(jbVisible) { 625 // Only proceed if we're currently showing the JB model 626 if (!jbTerrainModel || !document.getElementById('jbmodel-container') || 627 document.getElementById('jbmodel-container').style.display === 'none') { 628 return; 629 } 630 631 jbLog(`Setting satellite layer visibility to: ${jbVisible}`); 632 let jbMeshesUpdated = 0; 633 634 jbTerrainModel.traverse((jbChild) => { 635 if (jbChild instanceof THREE.Mesh && jbChild.material) { 636 // Check if material is an array 637 if (Array.isArray(jbChild.material)) { 638 jbChild.material.forEach(jbMat => { 639 if (jbMat.name === 'satellite_material') { 640 jbMat.opacity = jbVisible ? 1.0 : 0.0; 641 jbMat.transparent = true; 642 jbMat.needsUpdate = true; 643 jbMeshesUpdated++; 644 } 645 }); 646 } 647 // Single material 648 else if (jbChild.material.name === 'satellite_material') { 649 jbChild.material.opacity = jbVisible ? 1.0 : 0.0; 650 jbChild.material.transparent = true; 651 jbChild.material.needsUpdate = true; 652 jbMeshesUpdated++; 653 } 654 } 655 }); 656 657 jbLog(`Updated visibility for ${jbMeshesUpdated} satellite materials`); 658} 659 660function jbToggleSatelliteLayer() { 661 if (!jbTerrainModel) { 662 jbLog('Terrain model not initialized yet', 'warn'); 663 return; 664 } 665 666 const jbButton = document.getElementById('jb-satelliteToggle'); 667 if (!jbButton) { 668 jbLog('Satellite toggle button not found!', 'error'); 669 return; 670 } 671 672 // Determine current visibility state 673 let jbIsVisible = false; 674 jbTerrainModel.traverse((jbChild) => { 675 if (jbChild instanceof THREE.Mesh && jbChild.material) { 676 if (Array.isArray(jbChild.material)) { 677 jbChild.material.forEach(jbMat => { 678 if (jbMat.name === 'satellite_material') { 679 jbIsVisible = jbMat.opacity > 0; 680 } 681 }); 682 } else if (jbChild.material.name === 'satellite_material') { 683 jbIsVisible = jbChild.material.opacity > 0; 684 } 685 } 686 }); 687 688 // Toggle visibility 689 const jbNewState = !jbIsVisible; 690 jbSetSatelliteLayerVisibility(jbNewState); 691 692 // Update button styling 693 jbButton.classList.toggle('active', jbNewState); 694 695 jbLog(`Satellite layer visibility toggled to: ${jbNewState}`); 696} 697 698function jbSetupLights() { 699 try { 700 jbScene.children.forEach(jbChild => { 701 if (jbChild instanceof THREE.Light) { 702 jbScene.remove(jbChild); 703 } 704 }); 705 706 const jbSunLight = new THREE.DirectionalLight(0xfffaf0, 1.1); 707 jbSunLight.position.set(-600, 400, 200); 708 jbSunLight.castShadow = true; 709 710 const jbSunTarget = new THREE.Object3D(); 711 jbSunTarget.position.set(100, 0, -100); 712 jbScene.add(jbSunTarget); 713 jbSunLight.target = jbSunTarget; 714 jbSunLight.target.updateMatrixWorld(); 715 716 jbSunLight.shadow.mapSize.width = 4096; 717 jbSunLight.shadow.mapSize.height = 4096; 718 jbSunLight.shadow.camera.near = 1; 719 jbSunLight.shadow.camera.far = 1000; 720 jbSunLight.shadow.camera.left = -200; 721 jbSunLight.shadow.camera.right = 200; 722 jbSunLight.shadow.camera.top = 200; 723 jbSunLight.shadow.camera.bottom = -200; 724 jbSunLight.shadow.bias = -0.0003; 725 jbSunLight.shadow.normalBias = 0.02; 726 jbSunLight.shadow.radius = 3; 727 728 jbScene.add(jbSunLight); 729 730 const jbAmbientLight = new THREE.AmbientLight(0xb9d5ff, 0.5); 731 jbScene.add(jbAmbientLight); 732 733 const jbHemisphereLight = new THREE.HemisphereLight(0xffeeb1, 0x080820, 0.1); 734 jbScene.add(jbHemisphereLight); 735 736 jbLog('Lights setup completed'); 737 } catch (error) { 738 jbLog(`Light setup failed: ${error.message}`, 'error'); 739 } 740} 741 742function jbOnWindowResize() {
743 const jbAspectRatio = jbContainer.clientWidth / jbContainer.clientHeight; 744 const jbViewSize = 200; 745 746 jbCamera.left = -jbViewSize * jbAspectRatio / 2; 747 jbCamera.right = jbViewSize * jbAspectRatio / 2; 748 jbCamera.top = jbViewSize / 2; 749 jbCamera.bottom = -jbViewSize / 2; 750 751 jbCamera.updateProjectionMatrix(); 752 jbRenderer.setSize(jbContainer.clientWidth, jbContainer.clientHeight); 753 jbLog(`Window resized to ${jbContainer.clientWidth}x${jbContainer.clientHeight}`); 754} 755 756 757function jbLog(jbMessage, jbType = 'info') { 758 const jbTimestamp = new Date().toISOString(); 759 const jbLogMessage = `[${jbTimestamp}] ${jbMessage}`; 760 761 switch(jbType) { 762 case 'error': 763 console.error(jbLogMessage); 764 break; 765 case 'warn': 766 console.warn(jbLogMessage); 767 break; 768 default: 769 console.log(jbLogMessage); 770 } 771} 772
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.