trajectory-tracker / frontend /visualizer.js
Redfire-1234's picture
Upload 4 files
052979d verified
class TrajectoryVisualizer {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.trajectories = [];
this.objects = [];
this.trails = [];
this.isPlaying = false;
this.currentFrame = 0;
this.maxFrames = 0;
this.showTrails = true;
this.init();
}
init() {
// Scene
this.scene = new THREE.Scene();
this.scene.fog = new THREE.Fog(0x1a1a2e, 5, 15);
// Camera
this.camera = new THREE.PerspectiveCamera(
75,
this.container.clientWidth / this.container.clientHeight,
0.1,
1000
);
this.camera.position.set(3, 3, 3);
this.camera.lookAt(0, 0, 0);
// Renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setClearColor(0x1a1a2e);
this.container.appendChild(this.renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 5, 5);
this.scene.add(directionalLight);
const pointLight = new THREE.PointLight(0x667eea, 1, 100);
pointLight.position.set(0, 3, 0);
this.scene.add(pointLight);
// Grid helper
const gridHelper = new THREE.GridHelper(4, 20, 0x444444, 0x222222);
gridHelper.position.y = -1;
this.scene.add(gridHelper);
// Axes helper
const axesHelper = new THREE.AxesHelper(2);
this.scene.add(axesHelper);
// Handle window resize
window.addEventListener('resize', () => this.onWindowResize());
// Mouse controls
this.setupControls();
// Start animation loop
this.animate();
}
setupControls() {
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
this.container.addEventListener('mousedown', (e) => {
isDragging = true;
});
this.container.addEventListener('mousemove', (e) => {
if (isDragging) {
const deltaX = e.offsetX - previousMousePosition.x;
const deltaY = e.offsetY - previousMousePosition.y;
const rotationSpeed = 0.005;
this.camera.position.applyAxisAngle(
new THREE.Vector3(0, 1, 0),
deltaX * rotationSpeed
);
const lookAt = new THREE.Vector3(0, 0, 0);
this.camera.lookAt(lookAt);
}
previousMousePosition = { x: e.offsetX, y: e.offsetY };
});
this.container.addEventListener('mouseup', () => {
isDragging = false;
});
// Zoom with mouse wheel
this.container.addEventListener('wheel', (e) => {
e.preventDefault();
const zoomSpeed = 0.1;
const direction = new THREE.Vector3();
this.camera.getWorldDirection(direction);
if (e.deltaY < 0) {
this.camera.position.addScaledVector(direction, zoomSpeed);
} else {
this.camera.position.addScaledVector(direction, -zoomSpeed);
}
});
}
loadTrajectories(trajectories) {
this.trajectories = trajectories;
this.currentFrame = 0;
this.isPlaying = false;
// Find max frames
this.maxFrames = 0;
trajectories.forEach(traj => {
const maxFrame = Math.max(...traj.points.map(p => p.frame));
if (maxFrame > this.maxFrames) this.maxFrames = maxFrame;
});
// Clear existing objects
this.objects.forEach(obj => this.scene.remove(obj));
this.trails.forEach(line => this.scene.remove(line));
this.objects = [];
this.trails = [];
// Create objects for each trajectory
const colors = [0xff6b6b, 0x4ecdc4, 0xffe66d, 0x95e1d3, 0xf38181, 0xaa96da, 0xfcbad3, 0xa8e6cf];
trajectories.forEach((traj, index) => {
const color = colors[index % colors.length];
const points = traj.points;
// Create trail line
const positions = new Float32Array(points.length * 3);
points.forEach((point, i) => {
positions[i * 3] = point.x;
positions[i * 3 + 1] = point.y;
positions[i * 3 + 2] = point.z;
});
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const material = new THREE.LineBasicMaterial({
color: color,
linewidth: 2
});
const line = new THREE.Line(geometry, material);
line.geometry.setDrawRange(0, 0);
this.scene.add(line);
this.trails.push(line);
// Create sphere for current position
const sphereGeometry = new THREE.SphereGeometry(0.05, 16, 16);
const sphereMaterial = new THREE.MeshPhongMaterial({
color: color,
emissive: color,
emissiveIntensity: 0.5
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.visible = false;
this.scene.add(sphere);
this.objects.push(sphere);
});
}
play() {
this.isPlaying = true;
}
pause() {
this.isPlaying = false;
}
reset() {
this.currentFrame = 0;
this.isPlaying = false;
this.objects.forEach(obj => obj.visible = false);
this.trails.forEach(trail => {
trail.geometry.setDrawRange(0, 0);
});
}
setShowTrails(show) {
this.showTrails = show;
this.trails.forEach(trail => trail.visible = show);
}
update() {
if (this.isPlaying && this.trajectories.length > 0) {
this.currentFrame++;
// Update each trajectory
this.trajectories.forEach((traj, idx) => {
const points = traj.points;
const currentPoint = points.find(p => p.frame === this.currentFrame);
if (currentPoint) {
const obj = this.objects[idx];
obj.position.set(currentPoint.x, currentPoint.y, currentPoint.z);
obj.visible = true;
// Update trail
if (this.showTrails) {
const trail = this.trails[idx];
const visiblePoints = points.filter(p => p.frame <= this.currentFrame);
trail.geometry.setDrawRange(0, visiblePoints.length);
}
}
});
// Loop animation
if (this.currentFrame >= this.maxFrames) {
this.currentFrame = 0;
}
}
}
animate() {
requestAnimationFrame(() => this.animate());
this.update();
this.renderer.render(this.scene, this.camera);
}
onWindowResize() {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
}
destroy() {
window.removeEventListener('resize', () => this.onWindowResize());
this.renderer.dispose();
while(this.container.firstChild) {
this.container.removeChild(this.container.firstChild);
}
}
}