X
Y
Z
|0⟩
|1⟩

Controls:

Left Mouse: Rotate view

Right Mouse: Drag qubit state

Scroll: Zoom in/out

$$|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$$
θ = 45.0°
φ = 0.0°
α = 0.924
β = 0.383
Status: Loading...
, '
X
Y
Z
|0⟩
|1⟩

Controls:

Left Mouse: Rotate view

Right Mouse: Drag qubit state

Scroll: Zoom in/out

$$|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$$
θ = 45.0°
φ = 0.0°
α = 0.924
β = 0.383
Status: Loading...
], ['\\(', '\\)']], displayMath: [['$', '$'], ['\\[', '\\]']] }, startup: { ready() { console.log('MathJax is loaded, but not yet initialized'); MathJax.startup.defaultReady(); console.log('MathJax is initialized, and the initial typeset is queued'); } } };
X
Y
Z
|0⟩
|1⟩

Controls:

Left Mouse: Rotate view

Right Mouse: Drag qubit state

Scroll: Zoom in/out

$$|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$$
θ = 45.0°
φ = 0.0°
α = 0.924
β = 0.383
Status: Loading...
, ' // Global variables let scene, camera, renderer, controls; let sphere, qubitArrow, qubitGroup; let isDragging = false; let mouse = new THREE.Vector2(); let raycaster = new THREE.Raycaster(); // Current qubit state in spherical coordinates let currentTheta = Math.PI / 4; // 45 degrees let currentPhi = 0; // 0 degrees // Label positions for 3D to 2D projection const labelPositions = { x: new THREE.Vector3(1.5, 0, 0), y: new THREE.Vector3(0, 0, 1.5), z: new THREE.Vector3(0, 1.5, 0), north: new THREE.Vector3(0, 1.2, 0), south: new THREE.Vector3(0, -1.2, 0) }; // Simple OrbitControls implementation since CDN might fail class SimpleOrbitControls { constructor(camera, domElement) { this.camera = camera; this.domElement = domElement; this.enabled = true; this.enableDamping = true; this.dampingFactor = 0.05; this.spherical = new THREE.Spherical(); this.sphericalDelta = new THREE.Spherical(); this.scale = 1; this.panOffset = new THREE.Vector3(); this.zoomChanged = false; this.rotateStart = new THREE.Vector2(); this.rotateEnd = new THREE.Vector2(); this.rotateDelta = new THREE.Vector2(); this.target = new THREE.Vector3(); this.isRotating = false; this.addEventListeners(); this.update(); } addEventListeners() { this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this)); this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this)); this.domElement.addEventListener('mouseup', this.onMouseUp.bind(this)); this.domElement.addEventListener('wheel', this.onMouseWheel.bind(this)); } onMouseDown(event) { if (!this.enabled) return; if (event.button === 0) { // Left mouse button this.isRotating = true; this.rotateStart.set(event.clientX, event.clientY); } } onMouseMove(event) { if (!this.enabled) return; if (this.isRotating) { this.rotateEnd.set(event.clientX, event.clientY); this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart); const element = this.domElement; // Reduced rotation sensitivity by using smaller multipliers this.sphericalDelta.theta -= 1./270 * Math.PI * this.rotateDelta.x / element.clientHeight; this.sphericalDelta.phi -= 1./270 * Math.PI * this.rotateDelta.y / element.clientHeight; this.rotateStart.copy(this.rotateEnd); } } onMouseUp(event) { if (event.button === 0) { this.isRotating = false; } } onMouseWheel(event) { if (!this.enabled) return; if (event.deltaY < 0) { this.scale /= 1.1; } else if (event.deltaY > 0) { this.scale *= 1.1; } this.zoomChanged = true; } update() { const offset = new THREE.Vector3(); const quat = new THREE.Quaternion().setFromUnitVectors(this.camera.up, new THREE.Vector3(0, 1, 0)); const quatInverse = quat.clone().invert(); offset.copy(this.camera.position).sub(this.target); offset.applyQuaternion(quat); this.spherical.setFromVector3(offset); this.spherical.theta += this.sphericalDelta.theta; this.spherical.phi += this.sphericalDelta.phi; this.spherical.radius *= this.scale; // Restrict phi to be between desired limits this.spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, this.spherical.phi)); this.spherical.makeSafe(); offset.setFromSpherical(this.spherical); offset.applyQuaternion(quatInverse); this.camera.position.copy(this.target).add(offset); this.camera.lookAt(this.target); if (this.enableDamping) { this.sphericalDelta.theta *= (1 - this.dampingFactor); this.sphericalDelta.phi *= (1 - this.dampingFactor); } else { this.sphericalDelta.set(0, 0, 0); } this.scale = 1; return false; } } // Initialize the scene function init() { try { updateStatus("Initializing Three.js..."); // Check if Three.js loaded if (typeof THREE === 'undefined') { throw new Error('Three.js failed to load'); } // Create scene scene = new THREE.Scene(); scene.background = new THREE.Color(0xf0f0f0); updateStatus("Scene created"); // Create camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(3, 3, 3); updateStatus("Camera created"); // Create renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.getElementById('canvas-container').appendChild(renderer.domElement); updateStatus("Renderer created"); // Create controls controls = new SimpleOrbitControls(camera, renderer.domElement); updateStatus("Controls created"); // Create lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 5); directionalLight.castShadow = true; scene.add(directionalLight); updateStatus("Lighting added"); // Create Bloch sphere createBlochSphere(); updateStatus("Bloch sphere created"); // Create coordinate axes createAxes(); updateStatus("Axes created"); // Create qubit representation createQubit(); updateStatus("Qubit created"); // Add event listeners addEventListeners(); updateStatus("Event listeners added"); // Update initial state display updateQubitPosition(); updateStateDisplay(); updateStatus("Ready!"); // Hide debug panel after 3 seconds setTimeout(() => { document.getElementById('debug').style.display = 'none'; }, 3000); } catch (error) { console.error('Initialization error:', error); updateStatus("Error: " + error.message); } } function createBlochSphere() { // Main sphere (transparent light blue) const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); const sphereMaterial = new THREE.MeshLambertMaterial({ color: 0x87CEEB, transparent: true, opacity: 0.3 }); sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); scene.add(sphere); // Latitude lines for (let i = 1; i < 6; i++) { const phi = (i * Math.PI) / 6; const radius = Math.sin(phi); const y = Math.cos(phi); const curve = new THREE.EllipseCurve(0, 0, radius, radius, 0, 2 * Math.PI, false, 0); const points = curve.getPoints(64); const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: 0x000000 }); const line = new THREE.Line(geometry, material); line.position.y = y; line.rotation.x = Math.PI / 2; scene.add(line); // Southern hemisphere const line2 = line.clone(); line2.position.y = -y; scene.add(line2); } // Longitude lines for (let i = 0; i < 12; i++) { const angle = (i * Math.PI) / 6; const points = []; for (let j = 0; j <= 64; j++) { const phi = (j * Math.PI) / 64; const x = Math.sin(phi) * Math.cos(angle); const y = Math.cos(phi); const z = Math.sin(phi) * Math.sin(angle); points.push(new THREE.Vector3(x, y, z)); } const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: 0x000000 }); const line = new THREE.Line(geometry, material); scene.add(line); } } function createAxes() { const axisLength = 1.3; const arrowHeadLength = 0.1; const arrowHeadRadius = 0.03; // X-axis (red) const xGeometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(0, 0, 0), new THREE.Vector3(axisLength, 0, 0) ]); const xMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 }); const xAxis = new THREE.Line(xGeometry, xMaterial); scene.add(xAxis); // X-axis arrow head const xArrowGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8); const xArrowMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 }); const xArrow = new THREE.Mesh(xArrowGeometry, xArrowMaterial); xArrow.position.set(axisLength, 0, 0); xArrow.rotation.z = -Math.PI / 2; scene.add(xArrow); // Y-axis (green) - this is actually Z in our display const yGeometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, axisLength) ]); const yMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 }); const yAxis = new THREE.Line(yGeometry, yMaterial); scene.add(yAxis); // Y-axis arrow head const yArrowGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8); const yArrowMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00 }); const yArrow = new THREE.Mesh(yArrowGeometry, yArrowMaterial); yArrow.position.set(0, 0, axisLength); yArrow.rotation.x = Math.PI / 2; scene.add(yArrow); // Z-axis (blue) - this is actually Y in our display const zGeometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, axisLength, 0) ]); const zMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff }); const zAxis = new THREE.Line(zGeometry, zMaterial); scene.add(zAxis); // Z-axis arrow head const zArrowGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8); const zArrowMaterial = new THREE.MeshLambertMaterial({ color: 0x0000ff }); const zArrow = new THREE.Mesh(zArrowGeometry, zArrowMaterial); zArrow.position.set(0, axisLength, 0); scene.add(zArrow); } function createQubit() { // Create a group for the qubit qubitGroup = new THREE.Group(); // We'll create the arrow components here, but position them in updateQubitPosition() // Arrow shaft will be created as needed in updateQubitPosition() // Add group to scene scene.add(qubitGroup); // Set initial position updateQubitPosition(); } function updateQubitPosition() { // Clear previous arrow components while (qubitGroup.children.length > 0) { qubitGroup.remove(qubitGroup.children[0]); } // Spherical to Cartesian coordinate transformation // For unit sphere (r=1): x = sin(θ)cos(φ), y = cos(θ), z = sin(θ)sin(φ) const x = Math.sin(currentTheta) * Math.cos(currentPhi); const y = Math.cos(currentTheta); const z = Math.sin(currentTheta) * Math.sin(currentPhi); // Create arrow shaft as a line from origin to surface point const arrowPoints = [ new THREE.Vector3(0, 0, 0), // Origin new THREE.Vector3(x, y, z) // Surface point ]; const arrowGeometry = new THREE.BufferGeometry().setFromPoints(arrowPoints); const arrowMaterial = new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 3 }); const arrowLine = new THREE.Line(arrowGeometry, arrowMaterial); qubitGroup.add(arrowLine); // Create circular ball at the end of the line const ballGeometry = new THREE.SphereGeometry(0.06, 16, 16); const ballMaterial = new THREE.MeshLambertMaterial({ color: 0x000000 }); const ball = new THREE.Mesh(ballGeometry, ballMaterial); // Position the ball at the surface point ball.position.set(x, y, z); qubitGroup.add(ball); // Update the state display updateStateDisplay(); } function updateStateDisplay() { // Calculate α and β from θ and φ const alpha = Math.cos(currentTheta / 2); const beta = Math.sin(currentTheta / 2); // Update coordinate displays document.getElementById('theta-value').textContent = (currentTheta * 180 / Math.PI).toFixed(1) + '°'; document.getElementById('phi-value').textContent = (currentPhi * 180 / Math.PI).toFixed(1) + '°'; document.getElementById('alpha-value').textContent = alpha.toFixed(3); document.getElementById('beta-value').textContent = beta.toFixed(3); // Update LaTeX equation const alphaStr = alpha.toFixed(3); const betaStr = beta.toFixed(3); const phaseStr = currentPhi === 0 ? '' : `e^{i${(currentPhi).toFixed(2)}}`; const stateEquation = `$$|\\psi\\rangle = ${alphaStr} |0\\rangle + ${betaStr}${phaseStr} |1\\rangle$$`; document.getElementById('state-display').innerHTML = stateEquation; // Re-render MathJax if (window.MathJax && window.MathJax.typesetPromise) { MathJax.typesetPromise([document.getElementById('state-display')]).catch(err => console.log(err)); } } function updateLabels() { // Project 3D positions to 2D screen coordinates const vector = new THREE.Vector3(); // Update each label position Object.keys(labelPositions).forEach(labelId => { vector.copy(labelPositions[labelId]); vector.project(camera); const x = (vector.x * 0.5 + 0.5) * window.innerWidth; const y = (vector.y * -0.5 + 0.5) * window.innerHeight; const label = document.getElementById(`label-${labelId}`); if (label) { label.style.left = x + 'px'; label.style.top = y + 'px'; // Hide labels that are behind the camera label.style.display = vector.z > 1 ? 'none' : 'block'; } }); } function addEventListeners() { renderer.domElement.addEventListener('mousedown', onMouseDown); renderer.domElement.addEventListener('mousemove', onMouseMove); renderer.domElement.addEventListener('mouseup', onMouseUp); renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault()); window.addEventListener('resize', onWindowResize); } function onMouseDown(event) { if (event.button === 2) { // Right mouse button mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObject(sphere); if (intersects.length > 0) { isDragging = true; controls.enabled = false; } } } function onMouseMove(event) { if (isDragging) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObject(sphere); if (intersects.length > 0) { const point = intersects[0].point; point.normalize(); // Ensure it's on the unit sphere // Convert to spherical coordinates currentTheta = Math.acos(Math.max(-1, Math.min(1, point.y))); currentPhi = Math.atan2(point.z, point.x); // Ensure phi is in [0, 2π] if (currentPhi < 0) currentPhi += 2 * Math.PI; updateQubitPosition(); } } } function onMouseUp(event) { if (event.button === 2) { isDragging = false; controls.enabled = true; } } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); if (controls) { controls.update(); } if (renderer && scene && camera) { renderer.render(scene, camera); updateLabels(); // Update label positions each frame } } // Start the application when page loads window.addEventListener('load', () => { init(); animate(); }); // Fallback initialization setTimeout(() => { if (document.getElementById('status').textContent === 'Loading...') { init(); animate(); } }, 1000); , '
X
Y
Z
|0⟩
|1⟩

Controls:

Left Mouse: Rotate view

Right Mouse: Drag qubit state

Scroll: Zoom in/out

$$|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$$
θ = 45.0°
φ = 0.0°
α = 0.924
β = 0.383
Status: Loading...
], ['\\(', '\\)']], displayMath: [['$', '$'], ['\\[', '\\]']] }, startup: { ready() { console.log('MathJax is loaded, but not yet initialized'); MathJax.startup.defaultReady(); console.log('MathJax is initialized, and the initial typeset is queued'); } } };
X
Y
Z
|0⟩
|1⟩

Controls:

Left Mouse: Rotate view

Right Mouse: Drag qubit state

Scroll: Zoom in/out

$$|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$$
θ = 45.0°
φ = 0.0°
α = 0.924
β = 0.383
Status: Loading...
], ['\\(', '\\)']], displayMath: [['$', '$'], ['\\[', '\\]']] }, startup: { ready() { console.log('MathJax is loaded, but not yet initialized'); MathJax.startup.defaultReady(); console.log('MathJax is initialized, and the initial typeset is queued'); } } }; // Global variables let scene, camera, renderer, controls; let sphere, qubitArrow, qubitGroup; let isDragging = false; let mouse = new THREE.Vector2(); let raycaster = new THREE.Raycaster(); // Current qubit state in spherical coordinates let currentTheta = Math.PI / 4; // 45 degrees let currentPhi = 0; // 0 degrees // Label positions for 3D to 2D projection const labelPositions = { x: new THREE.Vector3(1.5, 0, 0), y: new THREE.Vector3(0, 0, 1.5), z: new THREE.Vector3(0, 1.5, 0), north: new THREE.Vector3(0, 1.2, 0), south: new THREE.Vector3(0, -1.2, 0) }; // Simple OrbitControls implementation since CDN might fail class SimpleOrbitControls { constructor(camera, domElement) { this.camera = camera; this.domElement = domElement; this.enabled = true; this.enableDamping = true; this.dampingFactor = 0.05; this.spherical = new THREE.Spherical(); this.sphericalDelta = new THREE.Spherical(); this.scale = 1; this.panOffset = new THREE.Vector3(); this.zoomChanged = false; this.rotateStart = new THREE.Vector2(); this.rotateEnd = new THREE.Vector2(); this.rotateDelta = new THREE.Vector2(); this.target = new THREE.Vector3(); this.isRotating = false; this.addEventListeners(); this.update(); } addEventListeners() { this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this)); this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this)); this.domElement.addEventListener('mouseup', this.onMouseUp.bind(this)); this.domElement.addEventListener('wheel', this.onMouseWheel.bind(this)); } onMouseDown(event) { if (!this.enabled) return; if (event.button === 0) { // Left mouse button this.isRotating = true; this.rotateStart.set(event.clientX, event.clientY); } } onMouseMove(event) { if (!this.enabled) return; if (this.isRotating) { this.rotateEnd.set(event.clientX, event.clientY); this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart); const element = this.domElement; // Reduced rotation sensitivity by using smaller multipliers this.sphericalDelta.theta -= 1./270 * Math.PI * this.rotateDelta.x / element.clientHeight; this.sphericalDelta.phi -= 1./270 * Math.PI * this.rotateDelta.y / element.clientHeight; this.rotateStart.copy(this.rotateEnd); } } onMouseUp(event) { if (event.button === 0) { this.isRotating = false; } } onMouseWheel(event) { if (!this.enabled) return; if (event.deltaY < 0) { this.scale /= 1.1; } else if (event.deltaY > 0) { this.scale *= 1.1; } this.zoomChanged = true; } update() { const offset = new THREE.Vector3(); const quat = new THREE.Quaternion().setFromUnitVectors(this.camera.up, new THREE.Vector3(0, 1, 0)); const quatInverse = quat.clone().invert(); offset.copy(this.camera.position).sub(this.target); offset.applyQuaternion(quat); this.spherical.setFromVector3(offset); this.spherical.theta += this.sphericalDelta.theta; this.spherical.phi += this.sphericalDelta.phi; this.spherical.radius *= this.scale; // Restrict phi to be between desired limits this.spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, this.spherical.phi)); this.spherical.makeSafe(); offset.setFromSpherical(this.spherical); offset.applyQuaternion(quatInverse); this.camera.position.copy(this.target).add(offset); this.camera.lookAt(this.target); if (this.enableDamping) { this.sphericalDelta.theta *= (1 - this.dampingFactor); this.sphericalDelta.phi *= (1 - this.dampingFactor); } else { this.sphericalDelta.set(0, 0, 0); } this.scale = 1; return false; } } // Initialize the scene function init() { try { updateStatus("Initializing Three.js..."); // Check if Three.js loaded if (typeof THREE === 'undefined') { throw new Error('Three.js failed to load'); } // Create scene scene = new THREE.Scene(); scene.background = new THREE.Color(0xf0f0f0); updateStatus("Scene created"); // Create camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(3, 3, 3); updateStatus("Camera created"); // Create renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.getElementById('canvas-container').appendChild(renderer.domElement); updateStatus("Renderer created"); // Create controls controls = new SimpleOrbitControls(camera, renderer.domElement); updateStatus("Controls created"); // Create lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 5); directionalLight.castShadow = true; scene.add(directionalLight); updateStatus("Lighting added"); // Create Bloch sphere createBlochSphere(); updateStatus("Bloch sphere created"); // Create coordinate axes createAxes(); updateStatus("Axes created"); // Create qubit representation createQubit(); updateStatus("Qubit created"); // Add event listeners addEventListeners(); updateStatus("Event listeners added"); // Update initial state display updateQubitPosition(); updateStateDisplay(); updateStatus("Ready!"); // Hide debug panel after 3 seconds setTimeout(() => { document.getElementById('debug').style.display = 'none'; }, 3000); } catch (error) { console.error('Initialization error:', error); updateStatus("Error: " + error.message); } } function createBlochSphere() { // Main sphere (transparent light blue) const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); const sphereMaterial = new THREE.MeshLambertMaterial({ color: 0x87CEEB, transparent: true, opacity: 0.3 }); sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); scene.add(sphere); // Latitude lines for (let i = 1; i < 6; i++) { const phi = (i * Math.PI) / 6; const radius = Math.sin(phi); const y = Math.cos(phi); const curve = new THREE.EllipseCurve(0, 0, radius, radius, 0, 2 * Math.PI, false, 0); const points = curve.getPoints(64); const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: 0x000000 }); const line = new THREE.Line(geometry, material); line.position.y = y; line.rotation.x = Math.PI / 2; scene.add(line); // Southern hemisphere const line2 = line.clone(); line2.position.y = -y; scene.add(line2); } // Longitude lines for (let i = 0; i < 12; i++) { const angle = (i * Math.PI) / 6; const points = []; for (let j = 0; j <= 64; j++) { const phi = (j * Math.PI) / 64; const x = Math.sin(phi) * Math.cos(angle); const y = Math.cos(phi); const z = Math.sin(phi) * Math.sin(angle); points.push(new THREE.Vector3(x, y, z)); } const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: 0x000000 }); const line = new THREE.Line(geometry, material); scene.add(line); } } function createAxes() { const axisLength = 1.3; const arrowHeadLength = 0.1; const arrowHeadRadius = 0.03; // X-axis (red) const xGeometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(0, 0, 0), new THREE.Vector3(axisLength, 0, 0) ]); const xMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 }); const xAxis = new THREE.Line(xGeometry, xMaterial); scene.add(xAxis); // X-axis arrow head const xArrowGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8); const xArrowMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 }); const xArrow = new THREE.Mesh(xArrowGeometry, xArrowMaterial); xArrow.position.set(axisLength, 0, 0); xArrow.rotation.z = -Math.PI / 2; scene.add(xArrow); // Y-axis (green) - this is actually Z in our display const yGeometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, axisLength) ]); const yMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 }); const yAxis = new THREE.Line(yGeometry, yMaterial); scene.add(yAxis); // Y-axis arrow head const yArrowGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8); const yArrowMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00 }); const yArrow = new THREE.Mesh(yArrowGeometry, yArrowMaterial); yArrow.position.set(0, 0, axisLength); yArrow.rotation.x = Math.PI / 2; scene.add(yArrow); // Z-axis (blue) - this is actually Y in our display const zGeometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, axisLength, 0) ]); const zMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff }); const zAxis = new THREE.Line(zGeometry, zMaterial); scene.add(zAxis); // Z-axis arrow head const zArrowGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8); const zArrowMaterial = new THREE.MeshLambertMaterial({ color: 0x0000ff }); const zArrow = new THREE.Mesh(zArrowGeometry, zArrowMaterial); zArrow.position.set(0, axisLength, 0); scene.add(zArrow); } function createQubit() { // Create a group for the qubit qubitGroup = new THREE.Group(); // We'll create the arrow components here, but position them in updateQubitPosition() // Arrow shaft will be created as needed in updateQubitPosition() // Add group to scene scene.add(qubitGroup); // Set initial position updateQubitPosition(); } function updateQubitPosition() { // Clear previous arrow components while (qubitGroup.children.length > 0) { qubitGroup.remove(qubitGroup.children[0]); } // Spherical to Cartesian coordinate transformation // For unit sphere (r=1): x = sin(θ)cos(φ), y = cos(θ), z = sin(θ)sin(φ) const x = Math.sin(currentTheta) * Math.cos(currentPhi); const y = Math.cos(currentTheta); const z = Math.sin(currentTheta) * Math.sin(currentPhi); // Create arrow shaft as a line from origin to surface point const arrowPoints = [ new THREE.Vector3(0, 0, 0), // Origin new THREE.Vector3(x, y, z) // Surface point ]; const arrowGeometry = new THREE.BufferGeometry().setFromPoints(arrowPoints); const arrowMaterial = new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 3 }); const arrowLine = new THREE.Line(arrowGeometry, arrowMaterial); qubitGroup.add(arrowLine); // Create circular ball at the end of the line const ballGeometry = new THREE.SphereGeometry(0.06, 16, 16); const ballMaterial = new THREE.MeshLambertMaterial({ color: 0x000000 }); const ball = new THREE.Mesh(ballGeometry, ballMaterial); // Position the ball at the surface point ball.position.set(x, y, z); qubitGroup.add(ball); // Update the state display updateStateDisplay(); } function updateStateDisplay() { // Calculate α and β from θ and φ const alpha = Math.cos(currentTheta / 2); const beta = Math.sin(currentTheta / 2); // Update coordinate displays document.getElementById('theta-value').textContent = (currentTheta * 180 / Math.PI).toFixed(1) + '°'; document.getElementById('phi-value').textContent = (currentPhi * 180 / Math.PI).toFixed(1) + '°'; document.getElementById('alpha-value').textContent = alpha.toFixed(3); document.getElementById('beta-value').textContent = beta.toFixed(3); // Update LaTeX equation const alphaStr = alpha.toFixed(3); const betaStr = beta.toFixed(3); const phaseStr = currentPhi === 0 ? '' : `e^{i${(currentPhi).toFixed(2)}}`; const stateEquation = `$$|\\psi\\rangle = ${alphaStr} |0\\rangle + ${betaStr}${phaseStr} |1\\rangle$$`; document.getElementById('state-display').innerHTML = stateEquation; // Re-render MathJax if (window.MathJax && window.MathJax.typesetPromise) { MathJax.typesetPromise([document.getElementById('state-display')]).catch(err => console.log(err)); } } function updateLabels() { // Project 3D positions to 2D screen coordinates const vector = new THREE.Vector3(); // Update each label position Object.keys(labelPositions).forEach(labelId => { vector.copy(labelPositions[labelId]); vector.project(camera); const x = (vector.x * 0.5 + 0.5) * window.innerWidth; const y = (vector.y * -0.5 + 0.5) * window.innerHeight; const label = document.getElementById(`label-${labelId}`); if (label) { label.style.left = x + 'px'; label.style.top = y + 'px'; // Hide labels that are behind the camera label.style.display = vector.z > 1 ? 'none' : 'block'; } }); } function addEventListeners() { renderer.domElement.addEventListener('mousedown', onMouseDown); renderer.domElement.addEventListener('mousemove', onMouseMove); renderer.domElement.addEventListener('mouseup', onMouseUp); renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault()); window.addEventListener('resize', onWindowResize); } function onMouseDown(event) { if (event.button === 2) { // Right mouse button mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObject(sphere); if (intersects.length > 0) { isDragging = true; controls.enabled = false; } } } function onMouseMove(event) { if (isDragging) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObject(sphere); if (intersects.length > 0) { const point = intersects[0].point; point.normalize(); // Ensure it's on the unit sphere // Convert to spherical coordinates currentTheta = Math.acos(Math.max(-1, Math.min(1, point.y))); currentPhi = Math.atan2(point.z, point.x); // Ensure phi is in [0, 2π] if (currentPhi < 0) currentPhi += 2 * Math.PI; updateQubitPosition(); } } } function onMouseUp(event) { if (event.button === 2) { isDragging = false; controls.enabled = true; } } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); if (controls) { controls.update(); } if (renderer && scene && camera) { renderer.render(scene, camera); updateLabels(); // Update label positions each frame } } // Start the application when page loads window.addEventListener('load', () => { init(); animate(); }); // Fallback initialization setTimeout(() => { if (document.getElementById('status').textContent === 'Loading...') { init(); animate(); } }, 1000); , '
X
Y
Z
|0⟩
|1⟩

Controls:

Left Mouse: Rotate view

Right Mouse: Drag qubit state

Scroll: Zoom in/out

$$|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$$
θ = 45.0°
φ = 0.0°
α = 0.924
β = 0.383
Status: Loading...
], ['\\(', '\\)']], displayMath: [['$', '$'], ['\\[', '\\]']] }, startup: { ready() { console.log('MathJax is loaded, but not yet initialized'); MathJax.startup.defaultReady(); console.log('MathJax is initialized, and the initial typeset is queued'); } } };
X
Y
Z
|0⟩
|1⟩

Controls:

Left Mouse: Rotate view

Right Mouse: Drag qubit state

Scroll: Zoom in/out

$$|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$$
θ = 45.0°
φ = 0.0°
α = 0.924
β = 0.383
Status: Loading...