JIT
making a realistic microscope simulation for education
in the code below it is a microscope simulation for education so i need you to modify the microscope cmponent to make it realistic because in the below simulation there are problems like - the objective lenses are the same size so fix them they should represent different because they have different magnification poert - the course foucu nob and the stage y and x controllers these components are just hanging on air with out attaching to the body so change their place to the rightfull place and attach to the body - make the main body handler of themicroscope more relistic because this looks skinny and not look like a real microscope body or handle - the specimen slide doesnt contain any component so let it contain teh onion specimen and for the user when viewing via themicroscope create svg or component that tricks users they are seeing different view for all or for the 4 magnifiers and also make it realistic when specimen or stage move so does the object when magnified - make the design and color of the 4objective lenses realistic and same color as the body - so user can see the onion with 4 lenses and different view for all as they function and can change the positions as well - also for mobole users they might not get keyboard so change the logic to let the components be changed and moved by user grabbing them - when magnification is changed the objective lenses rotate and change smoothly as well - the current vieing objective lense is above the specimen stage initially at the center above the stage al handles and component must be attached to the main body handles - the objective 4 lenses must be attached to the nose piece this occurs in my code but not surround it they must attach on the nose pies bottom surfase not on its side - change the surrounding theam to brigght smooth hospital theam - create a realistic viwing of componnets when magnified a movable colorfull 3d copoents to trick the user so learn what is inside an onlion and view -remember only html5 no frame work <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Interactive 3D Microscope - Onion Cell Experiment</title> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: url('lab-background.jpg') center/cover no-repeat, linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); overflow: hidden; color: #ecf0f1; height: 100vh; } canvas { display: block; cursor: grab; } canvas:active { cursor: grabbing; } /* Loading Screen */ .loading { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 9999; transition: opacity 0.8s ease; } .loading.hidden { opacity: 0; pointer-events: none; } /* Controls Panel */ .controls-panel { position: fixed; right: 10px; top: 10px; width: 280px; max-height: calc(100vh - 20px); background: rgba(26, 26, 46, 0.98); backdrop-filter: blur(20px); border-radius: 12px; padding: 15px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.15); overflow-y: auto; z-index: 1000; transition: all 0.3s ease; transform: translateX(0); } .controls-panel.minimized { width: 60px; padding: 8px; transform: translateX(0); } .controls-panel.minimized .panel-content { display: none; } .controls-panel.minimized .toggle-btn { transform: rotate(180deg); } .toggle-btn { position: absolute; top: 15px; right: 15px; background: rgba(52, 152, 219, 0.2); border: 1px solid rgba(52, 152, 219, 0.5); color: #3498db; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; transition: all 0.3s ease; z-index: 1001; } .toggle-btn:hover { background: rgba(52, 152, 219, 0.4); transform: scale(1.1); } .panel-header { text-align: center; margin-bottom: 25px; padding-bottom: 15px; border-bottom: 1px solid rgba(255,255,255,0.1); } .panel-title { color: #3498db; font-size: 20px; font-weight: 700; margin-bottom: 5px; } .panel-subtitle { color: #95a5a6; font-size: 14px; } .control-group { margin-bottom: 25px; } .control-group h3 { color: #3498db; margin-bottom: 15px; font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; } .control-group h3::before { content: ''; width: 4px; height: 16px; background: linear-gradient(135deg, #3498db, #2980b9); border-radius: 2px; } .control-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding: 12px 15px; background: rgba(255,255,255,0.05); border-radius: 10px; transition: all 0.3s ease; border: 1px solid transparent; } .control-item:hover { background: rgba(255,255,255,0.1); border-color: rgba(52, 152, 219, 0.3); transform: translateY(-1px); } .control-label { font-size: 14px; color: #ecf0f1; font-weight: 500; } .control-value { font-size: 14px; color: #3498db; font-weight: 700; font-family: 'Courier New', monospace; } .instruction-step { padding: 15px; margin: 10px 0; background: rgba(52, 152, 219, 0.1); border-left: 4px solid #3498db; border-radius: 0 10px 10px 0; font-size: 14px; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; } .instruction-step::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, rgba(52, 152, 219, 0.1), transparent); opacity: 0; transition: opacity 0.3s ease; } .instruction-step:hover::before { opacity: 1; } .instruction-step:hover { background: rgba(52, 152, 219, 0.2); transform: translateX(5px); } .instruction-step.active { background: rgba(52, 152, 219, 0.3); border-left-color: #2980b9; box-shadow: 0 5px 15px rgba(52, 152, 219, 0.2); } .instruction-step.completed { background: rgba(39, 174, 96, 0.2); border-left-color: #27ae60; color: #2ecc71; } /* Camera Controls */ .camera-controls { position: fixed; bottom: 10px; left: 10px; background: rgba(26, 26, 46, 0.98); backdrop-filter: blur(20px); border-radius: 8px; padding: 10px; border: 1px solid rgba(255,255,255,0.15); z-index: 1000; transition: all 0.3s ease; } .camera-controls.minimized { width: 50px; height: 50px; padding: 5px; border-radius: 50%; } .camera-controls.minimized h4, .camera-controls.minimized .camera-button:not(.toggle-cam) { display: none; } .progress-tracker { position: fixed; top: 10px; left: 10px; background: rgba(26, 26, 46, 0.98); backdrop-filter: blur(20px); border-radius: 12px; padding: 15px; border: 1px solid rgba(255,255,255,0.15); z-index: 1000; min-width: 250px; } .progress-step { display: flex; align-items: center; margin-bottom: 8px; padding: 8px; border-radius: 6px; transition: all 0.3s ease; } .progress-step.completed { background: rgba(39, 174, 96, 0.2); border-left: 3px solid #27ae60; } .progress-step.active { background: rgba(52, 152, 219, 0.2); border-left: 3px solid #3498db; animation: pulse 2s infinite; } .progress-step.pending { background: rgba(149, 165, 166, 0.1); border-left: 3px solid #95a5a6; } .step-number { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; background: rgba(52, 152, 219, 0.3); color: #3498db; font-size: 12px; font-weight: bold; margin-right: 10px; } .step-number.completed { background: #27ae60; color: white; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } .camera-controls h4 { color: #3498db; margin-bottom: 10px; font-size: 14px; font-weight: 600; } .camera-button { background: rgba(52, 152, 219, 0.2); border: 1px solid #3498db; color: #3498db; padding: 8px 12px; margin: 4px; border-radius: 6px; cursor: pointer; font-size: 12px; transition: all 0.2s ease; } .camera-button:hover { background: rgba(52, 152, 219, 0.3); transform: translateY(-1px); } .camera-button.active { background: #3498db; color: white; } /* Eyepiece View */ .eyepiece-view { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 450px; height: 450px; background: radial-gradient(circle, #000 0%, #1a1a1a 70%, #333 100%); border-radius: 50%; border: 12px solid #2c3e50; display: none; z-index: 2000; box-shadow: 0 0 60px rgba(0,0,0,0.8), inset 0 0 30px rgba(0,0,0,0.5); } .eyepiece-view.active { display: block; animation: zoomIn 0.4s ease-out; } @keyframes zoomIn { from { transform: translate(-50%, -50%) scale(0.8); opacity: 0; } to { transform: translate(-50%, -50%) scale(1); opacity: 1; } } .field-of-view { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 350px; height: 350px; background: #f8f9fa; border-radius: 50%; overflow: hidden; border: 3px solid #34495e; box-shadow: inset 0 0 20px rgba(0,0,0,0.3); } .specimen-view { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 200%; height: 200%; background: url('onion-cells.jpg') center/cover no-repeat; opacity: 0.8; transition: all 0.3s ease; } .specimen-view.focused { opacity: 1; filter: brightness(110%) contrast(120%); } .specimen-view.blurred { opacity: 0.3; filter: blur(8px); } .crosshairs { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 120px; height: 120px; pointer-events: none; } .crosshairs::before, .crosshairs::after { content: ''; position: absolute; background: rgba(255,255,255,0.6); box-shadow: 0 0 2px rgba(0,0,0,0.5); } .crosshairs::before { top: 50%; left: 0; right: 0; height: 1px; transform: translateY(-50%); } .crosshairs::after { left: 50%; top: 0; bottom: 0; width: 1px; transform: translateX(-50%); } /* Status Bar */ .status-bar { position: fixed; bottom: 20px; right: 20px; background: rgba(26, 26, 46, 0.95); backdrop-filter: blur(15px); border-radius: 12px; padding: 12px 16px; border: 1px solid rgba(255,255,255,0.1); font-size: 14px; color: #3498db; z-index: 1000; } /* Responsive Design */ @media (max-width: 1024px) { .controls-panel { width: 240px; font-size: 12px; right: 5px; top: 5px; } .progress-tracker { left: 5px; top: 5px; min-width: 200px; font-size: 12px; } .camera-controls { bottom: 5px; left: 5px; } .eyepiece-view { width: 350px; height: 350px; } .field-of-view { width: 280px; height: 280px; } } @media (max-width: 768px) { .controls-panel { width: 200px; padding: 10px; } .controls-panel.minimized { width: 50px; } .progress-tracker { min-width: 180px; padding: 10px; } .camera-controls { padding: 8px; } .eyepiece-view { width: 280px; height: 280px; } .field-of-view { width: 220px; height: 220px; } } @media (max-width: 480px) { .controls-panel { width: 180px; font-size: 11px; } .progress-tracker { min-width: 160px; font-size: 11px; } .eyepiece-view { width: 250px; height: 250px; } .field-of-view { width: 200px; height: 200px; } } /* Tooltips */ .tooltip { position: absolute; background: rgba(0,0,0,0.9); color: white; padding: 8px 12px; border-radius: 6px; font-size: 12px; pointer-events: none; z-index: 10000; opacity: 0; transition: opacity 0.3s ease; } .tooltip.visible { opacity: 1; } /* Performance indicator */ .performance-stats { position: fixed; top: 20px; left: 20px; background: rgba(0,0,0,0.7); color: #00ff00; font-family: 'Courier New', monospace; font-size: 12px; padding: 10px; border-radius: 4px; display: none; } .performance-stats.visible { display: block; } /* Highlight effects */ .highlight-glow { animation: highlightPulse 2s infinite; } @keyframes highlightPulse { 0%, 100% { filter: drop-shadow(0 0 10px rgba(52, 152, 219, 0.6)); } 50% { filter: drop-shadow(0 0 20px rgba(52, 152, 219, 1)); } } </style> </head> <body> <!-- Loading Screen --> <div class="loading" id="loading"> <div class="spinner"></div> <div class="loading-text"> Loading 3D Microscope Simulation...<br> <small>Preparing realistic components and lighting</small> </div> </div> <!-- Performance Stats (hidden by default) --> <div class="performance-stats" id="performance-stats"> FPS: <span id="fps">0</span><br> Triangles: <span id="triangles">0</span><br> Draw Calls: <span id="draw-calls">0</span> </div> <!-- Progress Tracker --> <div class="progress-tracker" id="progress-tracker"> <h4 style="color: #3498db; margin-bottom: 15px; font-size: 14px;">Experiment Progress</h4> <div class="progress-step active" data-step="0"> <span class="step-number">1</span> <span>Prepare specimen slide</span> </div> <div class="progress-step pending" data-step="1"> <span class="step-number">2</span> <span>Place slide on stage</span> </div> <div class="progress-step pending" data-step="2"> <span class="step-number">3</span> <span>Adjust coarse focus</span> </div> <div class="progress-step pending" data-step="3"> <span class="step-number">4</span> <span>Fine focus adjustment</span> </div> <div class="progress-step pending" data-step="4"> <span class="step-number">5</span> <span>Change magnification</span> </div> <div class="progress-step pending" data-step="5"> <span class="step-number">6</span> <span>Observe cells</span> </div> </div> <!-- Controls Panel --> <div class="controls-panel" id="controls-panel"> <button class="toggle-btn" id="toggle-controls">▶</button> <div class="panel-content"> <div class="panel-header"> <div class="panel-title">Microscope Controls</div> <div class="panel-subtitle">Interactive Settings</div> </div> <div class="control-group"> <h3>Current Settings</h3> <div class="control-item"> <span class="control-label">Magnification</span> <span class="control-value" id="current-mag">4x</span> </div> <div class="control-item"> <span class="control-label">Focus</span> <span class="control-value" id="current-focus">0.0</span> </div> <div class="control-item"> <span class="control-label">Brightness</span> <span class="control-value" id="current-brightness">75%</span> </div> <div class="control-item"> <span class="control-label">Stage Position</span> <span class="control-value" id="stage-position">0, 0</span> </div> </div> <div class="control-group"> <h3>Quick Actions</h3> <div class="control-item" style="cursor: pointer;" onclick="resetExperiment()"> <span class="control-label">🔄 Reset Experiment</span> </div> <div class="control-item" style="cursor: pointer;" onclick="toggleEyepiece()"> <span class="control-label">👁️ Toggle Eyepiece</span> </div> <div class="control-item" style="cursor: pointer;" onclick="autoFocus()"> <span class="control-label">🎯 Auto Focus</span> </div> </div> <div class="control-group"> <h3>Keyboard Shortcuts</h3> <div class="control-item"> <span class="control-label">Arrow Keys</span> <span class="control-value">Move Stage</span> </div> <div class="control-item"> <span class="control-label">+/-</span> <span class="control-value">Focus</span> </div> <div class="control-item"> <span class="control-label">1-4</span> <span class="control-value">Objectives</span> </div> <div class="control-item"> <span class="control-label">Space</span> <span class="control-value">Eyepiece</span> </div> <div class="control-item"> <span class="control-label">R</span> <span class="control-value">Reset View</span> </div> </div> </div> </div> <!-- Camera Controls --> <div class="camera-controls" id="camera-controls"> <button class="camera-button toggle-cam" onclick="toggleCameraControls()">📷</button> <div class="camera-content"> <h4>View Controls</h4> <button class="camera-button active" id="orbit-btn">🔄 Orbit</button> <button class="camera-button" id="pan-btn">✋ Pan</button> <button class="camera-button" id="zoom-btn">🔍 Zoom</button> <button class="camera-button" id="reset-btn">🏠 Reset</button> </div> </div> <!-- Status Bar --> <div class="status-bar" id="status-bar"> Click and drag to orbit • Scroll to zoom • Right-click to pan </div> <!-- Eyepiece View --> <div class="eyepiece-view" id="eyepiece-view"> <div class="field-of-view"> <div class="specimen-view" id="specimen-view"></div> <div class="crosshairs"></div> </div> </div> <!-- Tooltip --> <div class="tooltip" id="tooltip"></div> <!-- Three.js Library --> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script> class Realistic3DMicroscope { constructor() { // Initialize state this.state = { magnification: 4, focus: 0, brightness: 75, stagePosition: { x: 0, y: 0 }, eyepieceActive: false, currentStep: 0, isInteracting: false, cameraMode: 'orbit' }; // Magnification settings this.magnifications = { 4: { scale: 1, blur: 0, color: '#e74c3c' }, 10: { scale: 2.5, blur: 0, color: '#f39c12' }, 40: { scale: 10, blur: 0, color: '#27ae60' }, 100: { scale: 25, blur: 0, color: '#8e44ad' } }; // Performance monitoring this.clock = new THREE.Clock(); this.frameCount = 0; this.lastTime = 0; this.init(); } async init() { await this.setupScene(); await this.createMicroscope(); this.setupLighting(); this.setupControls(); this.setupEventListeners(); this.setupPerformanceMonitoring(); this.animate(); // Hide loading screen setTimeout(() => { document.getElementById('loading').classList.add('hidden'); this.updateStatusBar('3D Microscope loaded successfully!'); this.initializeExperiment(); }, 2000); } async setupScene() { // Scene this.scene = new THREE.Scene(); this.scene.background = new THREE.Color(0x0f1419); this.scene.fog = new THREE.Fog(0x0f1419, 50, 200); // Camera this.camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.camera.position.set(15, 12, 20); this.camera.lookAt(0, 5, 0); // Renderer this.renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; this.renderer.toneMapping = THREE.ACESFilmicToneMapping; this.renderer.toneMappingExposure = 1.2; this.renderer.outputEncoding = THREE.sRGBEncoding; document.body.appendChild(this.renderer.domElement); // Raycaster for interaction this.raycaster = new THREE.Raycaster(); this.mouse = new THREE.Vector2(); // Groups for organization this.microscopeGroup = new THREE.Group(); this.scene.add(this.microscopeGroup); } async createMicroscope() { await this.createBase(); await this.createArm(); await this.createStage(); await this.createTube(); await this.createEyepiece(); await this.createNosepiece(); await this.createObjectives(); await this.createFocusKnobs(); await this.createIlluminator(); await this.createEnvironment(); } async createBase() { // Main base const baseGeometry = new THREE.CylinderGeometry(6, 6.5, 1.5, 32); const baseMaterial = new THREE.MeshPhysicalMaterial({ color: 0x2c3e50, metalness: 0.8, roughness: 0.2, clearcoat: 1.0, clearcoatRoughness: 0.1 }); this.base = new THREE.Mesh(baseGeometry, baseMaterial); this.base.position.y = 0.75; this.base.castShadow = true; this.base.receiveShadow = true; this.microscopeGroup.add(this.base); // Base details const detailGeometry = new THREE.CylinderGeometry(5.5, 5.5, 0.3, 32); const detailMaterial = new THREE.MeshPhysicalMaterial({ color: 0x3498db, metalness: 0.9, roughness: 0.1, emissive: 0x1e5a96, emissiveIntensity: 0.1 }); const detail = new THREE.Mesh(detailGeometry, detailMaterial); detail.position.y = 1.3; this.microscopeGroup.add(detail); // Brand marking const loader = new THREE.FontLoader(); // Note: In a real implementation, you'd load a font file // For this demo, we'll add a simple geometric brand mark const brandGeometry = new THREE.CylinderGeometry(1, 1, 0.1, 8); const brandMaterial = new THREE.MeshPhysicalMaterial({ color: 0x34495e, metalness: 0.7, roughness: 0.3 }); const brand = new THREE.Mesh(brandGeometry, brandMaterial); brand.position.set(0, 1.45, 0); brand.rotation.y = Math.PI / 8; this.microscopeGroup.add(brand); } async createArm() { // Create arm using curve const points = [ new THREE.Vector3(0, 1.5, 0), new THREE.Vector3(-2, 4, 0), new THREE.Vector3(-2.5, 8, 0), new THREE.Vector3(-1, 12, 0), new THREE.Vector3(0, 14, 0) ]; const curve = new THREE.CatmullRomCurve3(points); const tubeGeometry = new THREE.TubeGeometry(curve, 50, 0.8, 16, false); const armMaterial = new THREE.MeshPhysicalMaterial({ color: 0x34495e, metalness: 0.8, roughness: 0.3, clearcoat: 0.5 }); this.arm = new THREE.Mesh(tubeGeometry, armMaterial); this.arm.castShadow = true; this.microscopeGroup.add(this.arm); } async createStage() { // Main stage platform const stageGeometry = new THREE.BoxGeometry(8, 0.5, 6); const stageMaterial = new THREE.MeshPhysicalMaterial({ color: 0xecf0f1, metalness: 0.1, roughness: 0.4, clearcoat: 0.8 }); this.stage = new THREE.Mesh(stageGeometry, stageMaterial); this.stage.position.set(0, 8, 0); this.stage.castShadow = true; this.stage.receiveShadow = true; this.stage.userData = { type: 'stage' }; this.microscopeGroup.add(this.stage); // Stage clips for (let i = 0; i < 2; i++) { const clipGeometry = new THREE.BoxGeometry(0.3, 0.3, 1.5); const clipMaterial = new THREE.MeshPhysicalMaterial({ color: 0x7f8c8d, metalness: 0.9, roughness: 0.1 }); const clip = new THREE.Mesh(clipGeometry, clipMaterial); clip.position.set(i === 0 ? -3.5 : 3.5, 8.4, 0); this.microscopeGroup.add(clip); } // Realistic glass slide const slideGeometry = new THREE.BoxGeometry(3, 0.08, 1); const slideMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0, roughness: 0.05, transmission: 0.98, transparent: true, opacity: 0.1, thickness: 0.5, ior: 1.5, clearcoat: 1.0, clearcoatRoughness: 0.0 }); this.slide = new THREE.Mesh(slideGeometry, slideMaterial); this.slide.position.set(0, 8.3, 0); this.slide.userData = { type: 'slide' }; this.slide.castShadow = true; this.slide.receiveShadow = true; this.microscopeGroup.add(this.slide); // Glass cover slip const coverGeometry = new THREE.BoxGeometry(1.5, 0.02, 0.8); const coverMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0, roughness: 0, transmission: 0.99, transparent: true, opacity: 0.05, ior: 1.52 }); const coverSlip = new THREE.Mesh(coverGeometry, coverMaterial); coverSlip.position.set(0, 8.36, 0); this.microscopeGroup.add(coverSlip); // Specimen on slide (geometry first) const specimenGeometry = new THREE.CircleGeometry(0.3, 32); // Load real onion sample texture const textureLoader = new THREE.TextureLoader(); textureLoader.load('onion-sample.jpg', (texture) => { const specimenMaterial = new THREE.MeshBasicMaterial({ map: texture, transparent: true, opacity: 0.8 }); this.specimen = new THREE.Mesh(specimenGeometry, specimenMaterial); this.specimen.position.set(0, 8.31, 0); this.specimen.rotation.x = -Math.PI / 2; this.microscopeGroup.add(this.specimen); }); // Stage control knobs this.createStageKnobs(); } createStageKnobs() { this.stageKnobs = []; const knobPositions = [ { x: 6, y: 8, z: 1.5, axis: 'x' }, { x: 6, y: 8, z: -1.5, axis: 'y' } ]; knobPositions.forEach((pos, index) => { const knobGroup = new THREE.Group(); // Knob body const knobGeometry = new THREE.CylinderGeometry(0.8, 0.8, 0.4, 32); const knobMaterial = new THREE.MeshPhysicalMaterial({ color: 0x34495e, metalness: 0.8, roughness: 0.2 }); const knob = new THREE.Mesh(knobGeometry, knobMaterial); knobGroup.add(knob); // Knob grip lines for (let i = 0; i < 8; i++) { const lineGeometry = new THREE.BoxGeometry(0.05, 0.3, 1.6); const lineMaterial = new THREE.MeshBasicMaterial({ color: 0x2c3e50 }); const line = new THREE.Mesh(lineGeometry, lineMaterial); line.rotation.y = (i / 8) * Math.PI * 2; knobGroup.add(line); } knobGroup.position.set(pos.x, pos.y, pos.z); knobGroup.rotation.z = Math.PI / 2; knobGroup.userData = { type: 'stageKnob', axis: pos.axis, originalRotation: knobGroup.rotation.clone() }; this.stageKnobs.push(knobGroup); this.microscopeGroup.add(knobGroup); }); } async createTube() { // Main tube const tubeGeometry = new THREE.CylinderGeometry(1.2, 1.2, 8, 32); const tubeMaterial = new THREE.MeshPhysicalMaterial({ color: 0x34495e, metalness: 0.8, roughness: 0.3 }); this.tube = new THREE.Mesh(tubeGeometry, tubeMaterial); this.tube.position.set(0, 18, 0); this.tube.castShadow = true; this.microscopeGroup.add(this.tube); } async createEyepiece() { const eyepieceGroup = new THREE.Group(); // Eyepiece body const bodyGeometry = new THREE.CylinderGeometry(1.5, 1.2, 3, 32); const bodyMaterial = new THREE.MeshPhysicalMaterial({ color: 0x2c3e50, metalness: 0.8, roughness: 0.2 }); const body = new THREE.Mesh(bodyGeometry, bodyMaterial); eyepieceGroup.add(body); // Eyepiece lens const lensGeometry = new THREE.CylinderGeometry(1.2, 1.2, 0.1, 32); const lensMaterial = new THREE.MeshPhysicalMaterial({ color: 0x3498db, metalness: 0, roughness: 0, transmission: 0.9, transparent: true }); const lens = new THREE.Mesh(lensGeometry, lensMaterial); lens.position.y = 1.4; eyepieceGroup.add(lens); eyepieceGroup.position.set(0, 24, 0); eyepieceGroup.userData = { type: 'eyepiece' }; this.eyepiece = eyepieceGroup; this.microscopeGroup.add(eyepieceGroup); } async createNosepiece() { // Nosepiece turret const nosepieceGeometry = new THREE.CylinderGeometry(2, 2, 1.5, 6); const nosepieceMaterial = new THREE.MeshPhysicalMaterial({ color: 0x34495e, metalness: 0.8, roughness: 0.2 }); this.nosepiece = new THREE.Mesh(nosepieceGeometry, nosepieceMaterial); this.nosepiece.position.set(0, 13, 0); this.nosepiece.castShadow = true; this.nosepiece.userData = { type: 'nosepiece', rotation: 0 }; this.microscopeGroup.add(this.nosepiece); } async createObjectives() { this.objectives = []; const magnifications = [4, 10, 40, 100]; const colors = [0xe74c3c, 0xf39c12, 0x27ae60, 0x8e44ad]; magnifications.forEach((mag, index) => { const objectiveGroup = new THREE.Group(); // Objective body const bodyGeometry = new THREE.CylinderGeometry(0.5, 0.6, 3, 16); const bodyMaterial = new THREE.MeshPhysicalMaterial({ color: colors[index], metalness: 0.8, roughness: 0.3 }); const body = new THREE.Mesh(bodyGeometry, bodyMaterial); objectiveGroup.add(body); // Objective lens const lensGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.2, 16); const lensMaterial = new THREE.MeshPhysicalMaterial({ color: 0x87ceeb, metalness: 0, roughness: 0, transmission: 0.8, transparent: true }); const lens = new THREE.Mesh(lensGeometry, lensMaterial); lens.position.y = -1.4; objectiveGroup.add(lens); // Position around nosepiece const angle = (index / magnifications.length) * Math.PI * 2; const radius = 2.5; objectiveGroup.position.set( Math.cos(angle) * radius, 13, Math.sin(angle) * radius ); objectiveGroup.userData = { type: 'objective', magnification: mag, active: mag === 4, originalPosition: objectiveGroup.position.clone(), angle: angle }; this.objectives.push(objectiveGroup); this.microscopeGroup.add(objectiveGroup); }); // Set initial active objective this.setActiveObjective(4); } async createFocusKnobs() { this.focusKnobs = []; const knobData = [ { type: 'coarse', size: 2, position: { x: 4, y: 16, z: 0 } }, { type: 'fine', size: 1.5, position: { x: 6.5, y: 16, z: 0 } } ]; knobData.forEach(data => { const knobGroup = new THREE.Group(); // Main knob const knobGeometry = new THREE.CylinderGeometry(data.size, data.size, 0.8, 32); const knobMaterial = new THREE.MeshPhysicalMaterial({ color: 0x34495e, metalness: 0.8, roughness: 0.2 }); const knob = new THREE.Mesh(knobGeometry, knobMaterial); knobGroup.add(knob); // Knob details for (let i = 0; i < 12; i++) { const detailGeometry = new THREE.BoxGeometry(0.1, 0.6, data.size * 2); const detailMaterial = new THREE.MeshBasicMaterial({ color: 0x2c3e50 }); const detail = new THREE.Mesh(detailGeometry, detailMaterial); detail.rotation.y = (i / 12) * Math.PI * 2; knobGroup.add(detail); } knobGroup.position.set(data.position.x, data.position.y, data.position.z); knobGroup.rotation.z = Math.PI / 2; knobGroup.userData = { type: 'focusKnob', focusType: data.type, originalRotation: knobGroup.rotation.clone() }; this.focusKnobs.push(knobGroup); this.microscopeGroup.add(knobGroup); }); } async createIlluminator() { const illuminatorGroup = new THREE.Group(); // Illuminator body const bodyGeometry = new THREE.CylinderGeometry(2, 2, 1.5, 32); const bodyMaterial = new THREE.MeshPhysicalMaterial({ color: 0xf39c12, metalness: 0.7, roughness: 0.3, emissive: 0xf39c12, emissiveIntensity: 0.1 }); const body = new THREE.Mesh(bodyGeometry, bodyMaterial); illuminatorGroup.add(body); // Light source this.illuminatorLight = new THREE.SpotLight(0xffffff, 1, 50, Math.PI / 6, 0.5); this.illuminatorLight.position.set(0, 2, 0); this.illuminatorLight.target.position.set(0, 8, 0); this.illuminatorLight.castShadow = true; this.illuminatorLight.shadow.mapSize.width = 1024; this.illuminatorLight.shadow.mapSize.height = 1024; illuminatorGroup.add(this.illuminatorLight); illuminatorGroup.add(this.illuminatorLight.target); illuminatorGroup.position.set(0, 4, 0); illuminatorGroup.userData = { type: 'illuminator' }; this.illuminator = illuminatorGroup; this.microscopeGroup.add(illuminatorGroup); } async createEnvironment() { // Lab table const tableGeometry = new THREE.BoxGeometry(50, 2, 30); const tableMaterial = new THREE.MeshPhysicalMaterial({ color: 0x8b4513, metalness: 0, roughness: 0.8 }); const table = new THREE.Mesh(tableGeometry, tableMaterial); table.position.set(0, -1, 0); table.receiveShadow = true; this.scene.add(table); // Lab equipment (decorative) this.createLabEquipment(); } createLabEquipment() { // Beakers for (let i = 0; i < 3; i++) { const beakerGeometry = new THREE.CylinderGeometry(1, 1.2, 3, 16); const beakerMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0, roughness: 0, transmission: 0.9, transparent: true }); const beaker = new THREE.Mesh(beakerGeometry, beakerMaterial); beaker.position.set(-15 + i * 5, 1.5, -10); beaker.castShadow = true; this.scene.add(beaker); } // Books for (let i = 0; i < 5; i++) { const bookGeometry = new THREE.BoxGeometry(1.5, 0.3, 2); const bookMaterial = new THREE.MeshPhysicalMaterial({ color: new THREE.Color().setHSL(Math.random(), 0.7, 0.5), metalness: 0, roughness: 0.8 }); const book = new THREE.Mesh(bookGeometry, bookMaterial); book.position.set(10 + i * 0.1, i * 0.3, 8); book.rotation.y = Math.random() * 0.2 - 0.1; book.castShadow = true; this.scene.add(book); } } setupLighting() { // Ambient light const ambientLight = new THREE.AmbientLight(0x404040, 0.3); this.scene.add(ambientLight); // Main directional light const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(20, 30, 20); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 100; directionalLight.shadow.camera.left = -30; directionalLight.shadow.camera.right = 30; directionalLight.shadow.camera.top = 30; directionalLight.shadow.camera.bottom = -30; this.scene.add(directionalLight); // Fill light const fillLight = new THREE.DirectionalLight(0x87ceeb, 0.3); fillLight.position.set(-20, 10, -20); this.scene.add(fillLight); // Rim light const rimLight = new THREE.DirectionalLight(0xffffff, 0.2); rimLight.position.set(0, 10, -30); this.scene.add(rimLight); } setupControls() { // Camera controls this.cameraTarget = new THREE.Vector3(0, 12, 0); this.cameraDistance = 25; this.cameraTheta = Math.PI / 6; this.cameraPhi = Math.PI / 3; this.updateCameraPosition(); } updateCameraPosition() { const x = this.cameraDistance * Math.sin(this.cameraPhi) * Math.cos(this.cameraTheta); const y = this.cameraDistance * Math.cos(this.cameraPhi) + this.cameraTarget.y; const z = this.cameraDistance * Math.sin(this.cameraPhi) * Math.sin(this.cameraTheta); this.camera.position.set(x, y, z); this.camera.lookAt(this.cameraTarget); } initializeExperiment() { this.experimentState = { currentStep: 0, steps: [ { name: 'Prepare specimen slide', completed: false, target: 'slide' }, { name: 'Place slide on stage', completed: false, target: 'stage' }, { name: 'Adjust coarse focus', completed: false, target: 'coarse-focus' }, { name: 'Fine focus adjustment', completed: false, target: 'fine-focus' }, { name: 'Change magnification', completed: false, target: 'objectives' }, { name: 'Observe cells', completed: false, target: 'eyepiece' } ] }; this.updateProgressTracker(); } updateProgressTracker() { const steps = document.querySelectorAll('.progress-step'); steps.forEach((step, index) => { const stepNumber = step.querySelector('.step-number'); if (index < this.experimentState.currentStep) { step.className = 'progress-step completed'; stepNumber.textContent = '✓'; stepNumber.className = 'step-number completed'; } else if (index === this.experimentState.currentStep) { step.className = 'progress-step active'; stepNumber.textContent = index + 1; stepNumber.className = 'step-number'; } else { step.className = 'progress-step pending'; stepNumber.textContent = index + 1; stepNumber.className = 'step-number'; } }); } completeCurrentStep() { if (this.experimentState.currentStep < this.experimentState.steps.length) { this.experimentState.steps[this.experimentState.currentStep].completed = true; this.experimentState.currentStep++; this.updateProgressTracker(); if (this.experimentState.currentStep >= this.experimentState.steps.length) { this.updateStatusBar('Experiment completed! Great job!'); setTimeout(() => { alert('🎉 Congratulations! You have successfully completed the onion cell observation experiment!'); }, 1000); } else { const nextStep = this.experimentState.steps[this.experimentState.currentStep]; this.updateStatusBar(`Next: ${nextStep.name}`); } } } toggleControlsPanel() { const panel = document.getElementById('controls-panel'); const toggleBtn = document.getElementById('toggle-controls'); panel.classList.toggle('minimized'); toggleBtn.textContent = panel.classList.contains('minimized') ? '▶' : '◀'; } updateSpecimenView() { const specimenView = document.getElementById('specimen-view'); if (!specimenView) return; // Update specimen view based on focus and magnification const focusQuality = Math.max(0, 1 - Math.abs(this.state.focus) * 0.1); const magnificationScale = 1 + (this.state.magnification - 4) * 0.1; specimenView.style.transform = `translate(-50%, -50%) scale(${magnificationScale})`; if (focusQuality > 0.8) { specimenView.className = 'specimen-view focused'; } else if (focusQuality < 0.3) { specimenView.className = 'specimen-view blurred'; } else { specimenView.className = 'specimen-view'; } } setupEventListeners() { // Mouse events this.renderer.domElement.addEventListener('mousedown', this.onMouseDown.bind(this)); this.renderer.domElement.addEventListener('mousemove', this.onMouseMove.bind(this)); this.renderer.domElement.addEventListener('mouseup', this.onMouseUp.bind(this)); this.renderer.domElement.addEventListener('wheel', this.onMouseWheel.bind(this)); this.renderer.domElement.addEventListener('contextmenu', e => e.preventDefault()); // Keyboard events document.addEventListener('keydown', this.onKeyDown.bind(this)); document.addEventListener('keyup', this.onKeyUp.bind(this)); // UI events document.getElementById('toggle-controls').addEventListener('click', this.toggleControlsPanel.bind(this)); document.getElementById('orbit-btn').addEventListener('click', () => this.setCameraMode('orbit')); document.getElementById('pan-btn').addEventListener('click', () => this.setCameraMode('pan')); document.getElementById('zoom-btn').addEventListener('click', () => this.setCameraMode('zoom')); document.getElementById('reset-btn').addEventListener('click', () => this.resetCamera()); // Window resize window.addEventListener('resize', this.onWindowResize.bind(this)); // Touch events for mobile this.setupTouchEvents(); } setupTouchEvents() { let touchStart = null; let touchCurrent = null; this.renderer.domElement.addEventListener('touchstart', (e) => { if (e.touches.length === 1) { touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY }; } }); this.renderer.domElement.addEventListener('touchmove', (e) => { e.preventDefault(); if (e.touches.length === 1 && touchStart) { touchCurrent = { x: e.touches[0].clientX, y: e.touches[0].clientY }; const deltaX = touchCurrent.x - touchStart.x; const deltaY = touchCurrent.y - touchStart.y; this.cameraTheta -= deltaX * 0.01; this.cameraPhi = Math.max(0.1, Math.min(Math.PI - 0.1, this.cameraPhi + deltaY * 0.01)); this.updateCameraPosition(); touchStart = touchCurrent; } }); this.renderer.domElement.addEventListener('touchend', () => { touchStart = null; touchCurrent = null; }); } onMouseDown(event) { this.mouseDown = true; this.mouseButton = event.button; this.lastMouseX = event.clientX; this.lastMouseY = event.clientY; // Check for object interaction this.checkObjectInteraction(event); } onMouseMove(event) { // Update mouse position for raycasting this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1; this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; if (this.mouseDown) { const deltaX = event.clientX - this.lastMouseX; const deltaY = event.clientY - this.lastMouseY; if (this.state.cameraMode === 'orbit' && this.mouseButton === 0) { // Orbit this.cameraTheta -= deltaX * 0.01; this.cameraPhi = Math.max(0.1, Math.min(Math.PI - 0.1, this.cameraPhi + deltaY * 0.01)); this.updateCameraPosition(); } else if (this.state.cameraMode === 'pan' || this.mouseButton === 2) { // Pan const panSpeed = 0.02; const right = new THREE.Vector3().crossVectors(this.camera.up, new THREE.Vector3().subVectors(this.camera.position, this.cameraTarget).normalize()); const up = new THREE.Vector3().crossVectors( new THREE.Vector3().subVectors(this.camera.position, this.cameraTarget).normalize(), right); this.cameraTarget.addScaledVector(right, -deltaX * panSpeed); this.cameraTarget.addScaledVector(up, deltaY * panSpeed); this.updateCameraPosition(); } this.lastMouseX = event.clientX; this.lastMouseY = event.clientY; } // Update tooltip this.updateTooltip(event); } onMouseUp(event) { this.mouseDown = false; this.state.isInteracting = false; } onMouseWheel(event) { event.preventDefault(); const zoomSpeed = 0.1; this.cameraDistance = Math.max(10, Math.min(50, this.cameraDistance + event.deltaY * zoomSpeed)); this.updateCameraPosition(); } checkObjectInteraction(event) { this.raycaster.setFromCamera(this.mouse, this.camera); const intersects = this.raycaster.intersectObjects(this.microscopeGroup.children, true); if (intersects.length > 0) { const object = intersects[0].object; let userData = object.userData; // Check parent userData if object doesn't have it if (!userData.type && object.parent) { userData = object.parent.userData; } this.handleObjectClick(userData, intersects[0].point); } } handleObjectClick(userData, point) { switch (userData.type) { case 'objective': this.changeObjective(userData.magnification); break; case 'eyepiece': this.toggleEyepiece(); break; case 'stageKnob': this.state.isInteracting = true; break; case 'focusKnob': this.state.isInteracting = true; break; case 'nosepiece': this.rotateNosepiece(); break; } } onKeyDown(event) { switch (event.code) { case 'ArrowLeft': this.moveStage(-0.1, 0); break; case 'ArrowRight': this.moveStage(0.1, 0); break; case 'ArrowUp': this.moveStage(0, 0.1); break; case 'ArrowDown': this.moveStage(0, -0.1); break; case 'Equal': case 'NumpadAdd': this.adjustFocus(1, 'fine'); break; case 'Minus': case 'NumpadSubtract': this.adjustFocus(-1, 'fine'); break; case 'Digit1': this.changeObjective(4); break; case 'Digit2': this.changeObjective(10); break; case 'Digit3': this.changeObjective(40); break; case 'Digit4': this.changeObjective(100); break; case 'Space': event.preventDefault(); this.toggleEyepiece(); break; case 'KeyR': this.resetCamera(); break; case 'Escape': if (this.state.eyepieceActive) { this.toggleEyepiece(); } break; } } onKeyUp(event) { // Handle key releases if needed } onWindowResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); } setupPerformanceMonitoring() { this.stats = { fps: 0, frameCount: 0, lastTime: performance.now() }; } updatePerformanceStats() { this.stats.frameCount++; const now = performance.now(); if (now >= this.stats.lastTime + 1000) { this.stats.fps = Math.round((this.stats.frameCount * 1000) / (now - this.stats.lastTime)); this.stats.frameCount = 0; this.stats.lastTime = now; // Update performance display if visible const fpsElement = document.getElementById('fps'); const trianglesElement = document.getElementById('triangles'); const drawCallsElement = document.getElementById('draw-calls'); if (fpsElement) fpsElement.textContent = this.stats.fps; if (trianglesElement) trianglesElement.textContent = this.renderer.info.render.triangles; if (drawCallsElement) drawCallsElement.textContent = this.renderer.info.render.calls; } } // Microscope functionality methods changeObjective(magnification) { this.state.magnification = magnification; this.setActiveObjective(magnification); this.updateUI(); this.updateSpecimenView(); this.updateStatusBar(`Magnification changed to ${magnification}x`); // Check if this completes step 5 if (this.experimentState.currentStep === 4) { this.completeCurrentStep(); } } setActiveObjective(magnification) { this.objectives.forEach(obj => { obj.userData.active = obj.userData.magnification === magnification; // Add visual feedback for active objective if (obj.userData.active) { obj.children[0].material.emissive.setHex(0x333333); obj.children[0].material.emissiveIntensity = 0.2; } else { obj.children[0].material.emissive.setHex(0x000000); obj.children[0].material.emissiveIntensity = 0; } }); } moveStage(deltaX, deltaY) { this.state.stagePosition.x += deltaX; this.state.stagePosition.y += deltaY; // Constrain movement this.state.stagePosition.x = Math.max(-2, Math.min(2, this.state.stagePosition.x)); this.state.stagePosition.y = Math.max(-2, Math.min(2, this.state.stagePosition.y)); // Move the stage visually if (this.stage) { this.stage.position.x = this.state.stagePosition.x; this.stage.position.z = this.state.stagePosition.y; } // Move slide and specimen with stage if (this.slide) { this.slide.position.x = this.state.stagePosition.x; this.slide.position.z = this.state.stagePosition.y; } if (this.specimen) { this.specimen.position.x = this.state.stagePosition.x; this.specimen.position.z = this.state.stagePosition.y; } this.updateUI(); this.updateStatusBar(`Stage position: ${this.state.stagePosition.x.toFixed(1)}, ${this.state.stagePosition.y.toFixed(1)}`); // Check if this completes step 2 if (this.experimentState.currentStep === 1) { this.completeCurrentStep(); } } adjustFocus(amount, type) { if (type === 'coarse') { this.state.focus += amount * 5; } else { this.state.focus += amount; } this.state.focus = Math.max(-50, Math.min(50, this.state.focus)); this.updateUI(); this.updateSpecimenView(); this.updateStatusBar(`${type} focus: ${this.state.focus.toFixed(1)}`); // Check if this completes step 3 or 4 if (type === 'coarse' && this.experimentState.currentStep === 2) { this.completeCurrentStep(); } else if (type === 'fine' && this.experimentState.currentStep === 3) { this.completeCurrentStep(); } } adjustBrightness(amount) { this.state.brightness += amount; this.state.brightness = Math.max(0, Math.min(100, this.state.brightness)); // Update illuminator intensity if (this.illuminatorLight) { this.illuminatorLight.intensity = this.state.brightness / 100; } this.updateUI(); this.updateStatusBar(`Brightness: ${this.state.brightness}%`); } toggleEyepiece() { this.state.eyepieceActive = !this.state.eyepieceActive; const eyepieceView = document.getElementById('eyepiece-view'); if (this.state.eyepieceActive) { eyepieceView.classList.add('active'); this.updateStatusBar('Viewing through eyepiece - Use ESC to exit'); // Check if this completes step 6 if (this.experimentState.currentStep === 5) { this.completeCurrentStep(); } } else { eyepieceView.classList.remove('active'); this.updateStatusBar('Eyepiece view closed'); } } rotateNosepiece() { // Find current active objective const currentIndex = this.objectives.findIndex(obj => obj.userData.active); const nextIndex = (currentIndex + 1) % this.objectives.length; const nextMagnification = this.objectives[nextIndex].userData.magnification; // Rotate nosepiece visually if (this.nosepiece) { this.nosepiece.rotation.y += Math.PI / 2; } this.changeObjective(nextMagnification); } setCameraMode(mode) { this.state.cameraMode = mode; // Update button states document.querySelectorAll('.camera-button').forEach(btn => { btn.classList.remove('active'); }); document.getElementById(`${mode}-btn`).classList.add('active'); this.updateStatusBar(`Camera mode: ${mode}`); } resetCamera() { this.cameraTarget = new THREE.Vector3(0, 12, 0); this.cameraDistance = 25; this.cameraTheta = Math.PI / 6; this.cameraPhi = Math.PI / 3; this.updateCameraPosition(); this.updateStatusBar('Camera reset to default position'); } updateUI() { // Update control panel values document.getElementById('current-mag').textContent = `${this.state.magnification}x`; document.getElementById('current-focus').textContent = this.state.focus.toFixed(1); document.getElementById('current-brightness').textContent = `${this.state.brightness}%`; document.getElementById('stage-position').textContent = `${this.state.stagePosition.x.toFixed(1)}, ${this.state.stagePosition.y.toFixed(1)}`; } updateStatusBar(message) { const statusBar = document.getElementById('status-bar'); if (statusBar) { statusBar.textContent = message; } } updateTooltip(event) { const tooltip = document.getElementById('tooltip'); if (!tooltip) return; this.raycaster.setFromCamera(this.mouse, this.camera); const intersects = this.raycaster.intersectObjects(this.microscopeGroup.children, true); if (intersects.length > 0) { const object = intersects[0].object; let userData = object.userData; // Check parent userData if object doesn't have it if (!userData.type && object.parent) { userData = object.parent.userData; } let tooltipText = ''; switch (userData.type) { case 'objective': tooltipText = `${userData.magnification}x Objective - Click to change magnification`; break; case 'eyepiece': tooltipText = 'Eyepiece - Click to view specimen (Space key)'; break; case 'stage': tooltipText = 'Stage - Use arrow keys to move'; break; case 'slide': tooltipText = 'Specimen slide - Contains onion cells'; break; case 'focusKnob': tooltipText = `${userData.focusType} focus knob - Use +/- keys`; break; case 'stageKnob': tooltipText = `Stage ${userData.axis}-axis control`; break; case 'nosepiece': tooltipText = 'Nosepiece - Click to rotate objectives'; break; case 'illuminator': tooltipText = 'Light source - Illuminates the specimen'; break; default: tooltipText = ''; } if (tooltipText) { tooltip.textContent = tooltipText; tooltip.style.left = `${event.clientX + 10}px`; tooltip.style.top = `${event.clientY - 10}px`; tooltip.classList.add('visible'); } else { tooltip.classList.remove('visible'); } } else { tooltip.classList.remove('visible'); } } animate() { requestAnimationFrame(this.animate.bind(this)); // Update performance stats this.updatePerformanceStats(); // Render the scene this.renderer.render(this.scene, this.camera); } } // Global functions for UI interactions function resetExperiment() { if (window.microscope) { window.microscope.experimentState.currentStep = 0; window.microscope.updateProgressTracker(); window.microscope.updateStatusBar('Experiment reset - Start with step 1'); } } function toggleEyepiece() { if (window.microscope) { window.microscope.toggleEyepiece(); } } function autoFocus() { if (window.microscope) { window.microscope.state.focus = 0; window.microscope.updateUI(); window.microscope.updateSpecimenView(); window.microscope.updateStatusBar('Auto focus complete'); } } function toggleCameraControls() { const controls = document.getElementById('camera-controls'); controls.classList.toggle('minimized'); } // Initialize the microscope when the page loads window.addEventListener('load', () => { window.microscope = new Realistic3DMicroscope(); }); </script> </body> </html>
Updated files
v1
the micoscope is not visile
Updated files
v2