PageSourceSearch

https://aurashkhawarzad.com/gallery/jbmodelgallery/jbmodelgallery2.js

js aurashkhawarzad.com collected 2026-09-29 22:49:38 UTC 27,261 bytes, 772 lines download raw bytes

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.