Spaces:
Running
Running
| // main.js | |
| import * as THREE from "https://unpkg.com/three@0.164.0/build/three.module.js"; | |
| import { VRButton } from "https://unpkg.com/three@0.164.0/examples/jsm/webxr/VRButton.js"; | |
| // ----- 1. Basic setup ----- | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x000000); | |
| const camera = new THREE.PerspectiveCamera( | |
| 70, | |
| window.innerWidth / window.innerHeight, | |
| 0.1, | |
| 1000 | |
| ); | |
| camera.position.set(0, 1.6, 3); // typical VR eye height | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.xr.enabled = true; | |
| document.body.appendChild(renderer.domElement); | |
| // Add VR button | |
| document.body.appendChild(VRButton.createButton(renderer)); | |
| // Simple light setup | |
| const light = new THREE.DirectionalLight(0xffffff, 0.8); | |
| light.position.set(5, 10, 7); | |
| scene.add(light); | |
| const ambient = new THREE.AmbientLight(0xffffff, 0.3); | |
| scene.add(ambient); | |
| // ----- 2. Sample graph data (replace with Neo4j later) ----- | |
| // Minimal example: 6 nodes, some edges | |
| const graphData = { | |
| nodes: [ | |
| { id: "A", label: "Node A" }, | |
| { id: "B", label: "Node B" }, | |
| { id: "C", label: "Node C" }, | |
| { id: "D", label: "Node D" }, | |
| { id: "E", label: "Node E" }, | |
| { id: "F", label: "Node F" } | |
| ], | |
| edges: [ | |
| { source: "A", target: "B" }, | |
| { source: "A", target: "C" }, | |
| { source: "B", target: "D" }, | |
| { source: "C", target: "D" }, | |
| { source: "C", target: "E" }, | |
| { source: "E", target: "F" } | |
| ] | |
| }; | |
| // ----- 3. Layout: place nodes around a circle ----- | |
| const nodeMap = new Map(); // id -> mesh | |
| const radius = 2; | |
| const nodeGeometry = new THREE.SphereGeometry(0.08, 16, 16); | |
| const nodeMaterial = new THREE.MeshStandardMaterial({ color: 0x00bcd4 }); | |
| graphData.nodes.forEach((node, index) => { | |
| const angle = (index / graphData.nodes.length) * Math.PI * 2; | |
| const x = radius * Math.cos(angle); | |
| const z = radius * Math.sin(angle); | |
| const y = 0; | |
| const mesh = new THREE.Mesh(nodeGeometry, nodeMaterial.clone()); | |
| mesh.position.set(x, y, z); | |
| mesh.userData = { id: node.id, label: node.label }; | |
| scene.add(mesh); | |
| nodeMap.set(node.id, mesh); | |
| }); | |
| // ----- 4. Draw edges as lines between node positions ----- | |
| const edgeMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 1 }); | |
| graphData.edges.forEach((edge) => { | |
| const sourceMesh = nodeMap.get(edge.source); | |
| const targetMesh = nodeMap.get(edge.target); | |
| if (!sourceMesh || !targetMesh) return; | |
| const points = []; | |
| points.push(sourceMesh.position.clone()); | |
| points.push(targetMesh.position.clone()); | |
| const edgeGeometry = new THREE.BufferGeometry().setFromPoints(points); | |
| const line = new THREE.Line(edgeGeometry, edgeMaterial); | |
| scene.add(line); | |
| }); | |
| // ----- 5. Subtle animation for visual interest ----- | |
| function animate() { | |
| renderer.setAnimationLoop(renderScene); | |
| } | |
| function renderScene() { | |
| // Slowly rotate the whole graph | |
| scene.rotation.y += 0.0015; | |
| renderer.render(scene, camera); | |
| } | |
| animate(); | |
| // ----- 6. Handle resize ----- | |
| window.addEventListener("resize", () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); |