Spaces:
Running
Running
| import * as THREE from 'three'; | |
| import { getElementPosition, ELEMENT_COLORS } from './elementLayout.js'; | |
| export class PeriodicTableComponent { | |
| constructor(scene, dataManager) { | |
| this.scene = scene; | |
| this.dataManager = dataManager; | |
| this.elementMeshes = new Map(); | |
| this.highlightedElement = null; | |
| this.selectedElement = null; | |
| } | |
| createPeriodicTable() { | |
| const elements = this.dataManager.getAllElements(); | |
| elements.forEach(symbol => { | |
| const elementData = this.dataManager.getElementData(symbol); | |
| const position = getElementPosition(symbol); | |
| if (!position) return; | |
| // Create element tile | |
| const geometry = new THREE.BoxGeometry(0.9, 0.9, 0.1); | |
| const color = ELEMENT_COLORS[elementData.category] || ELEMENT_COLORS.unknown; | |
| const material = new THREE.MeshStandardMaterial({ | |
| color: color, | |
| emissive: 0x000000, | |
| metalness: 0.3, | |
| roughness: 0.7 | |
| }); | |
| const mesh = new THREE.Mesh(geometry, material); | |
| mesh.position.set(position.x, position.y, position.z); | |
| mesh.userData = { symbol, elementData }; | |
| this.scene.add(mesh); | |
| this.elementMeshes.set(symbol, mesh); | |
| // Add text label | |
| this.addTextLabel(symbol, position); | |
| }); | |
| } | |
| addTextLabel(symbol, position) { | |
| const canvas = document.createElement('canvas'); | |
| const context = canvas.getContext('2d'); | |
| canvas.width = 128; | |
| canvas.height = 128; | |
| context.fillStyle = 'white'; | |
| context.font = 'bold 60px Arial'; | |
| context.textAlign = 'center'; | |
| context.textBaseline = 'middle'; | |
| context.fillText(symbol, 64, 64); | |
| const texture = new THREE.CanvasTexture(canvas); | |
| const spriteMaterial = new THREE.SpriteMaterial({ map: texture }); | |
| const sprite = new THREE.Sprite(spriteMaterial); | |
| sprite.scale.set(0.6, 0.6, 1); | |
| sprite.position.set(position.x, position.y, position.z + 0.1); | |
| this.scene.add(sprite); | |
| } | |
| highlightElement(symbol) { | |
| // Reset previous highlight | |
| if (this.highlightedElement && this.highlightedElement !== this.selectedElement) { | |
| const prevMesh = this.elementMeshes.get(this.highlightedElement); | |
| if (prevMesh) { | |
| prevMesh.material.emissive.setHex(0x000000); | |
| } | |
| } | |
| // Apply new highlight | |
| if (symbol && symbol !== this.selectedElement) { | |
| const mesh = this.elementMeshes.get(symbol); | |
| if (mesh) { | |
| mesh.material.emissive.setHex(0x444444); | |
| } | |
| } | |
| this.highlightedElement = symbol; | |
| } | |
| selectElement(symbol) { | |
| // Reset previous selection | |
| if (this.selectedElement) { | |
| const prevMesh = this.elementMeshes.get(this.selectedElement); | |
| if (prevMesh) { | |
| prevMesh.material.emissive.setHex(0x000000); | |
| } | |
| } | |
| // Apply new selection | |
| if (symbol) { | |
| const mesh = this.elementMeshes.get(symbol); | |
| if (mesh) { | |
| mesh.material.emissive.setHex(0x00ff00); | |
| } | |
| } | |
| this.selectedElement = symbol; | |
| } | |
| getElementAtPosition(x, y, camera) { | |
| const raycaster = new THREE.Raycaster(); | |
| const mouse = new THREE.Vector2(x, y); | |
| raycaster.setFromCamera(mouse, camera); | |
| const meshes = Array.from(this.elementMeshes.values()); | |
| const intersects = raycaster.intersectObjects(meshes); | |
| if (intersects.length > 0) { | |
| return intersects[0].object.userData.symbol; | |
| } | |
| return null; | |
| } | |
| getSelectedElement() { | |
| return this.selectedElement; | |
| } | |
| } | |