ros_robot_visualiser/script.js

188 lines
5.1 KiB
JavaScript

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import URDFLoader from './urdf/URDFLoader.js';
// Setup scene, camera, renderer
const canvas = document.getElementById('urdf-canvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(2, 2, 2);
camera.lookAt(0, 0, 0);
// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const directional = new THREE.DirectionalLight(0xffffff, 0.8);
directional.position.set(5, 10, 7);
scene.add(directional);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0.5, 0);
controls.update();
// Helpers
scene.add(new THREE.AxesHelper(1));
const gridHelper = new THREE.GridHelper(2, 10);
gridHelper.rotation.x = Math.PI / 2;
gridHelper.position.z = 0;
scene.add(gridHelper);
// Raycasting
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let hoveredJoint = null;
let isDragging = false;
let lastX = null;
let worldAxis = null;
let draggedJoint = null;
function findJointAncestor(object) {
while (object && object.parent) {
if (object.type === 'URDFJoint') return object;
object = object.parent;
}
return null;
}
// Load URDF
const loader = new URDFLoader();
loader.packages = { '': './urdf/' };
function findObjectByName(root, name) {
let result = null;
root.traverse(child => {
if (child.name === name) result = child;
});
return result;
}
loader.load('./urdf/sample.urdf', robot => {
//scene.rotation.x = -Math.PI / 2;
robot.rotation.x = -Math.PI / 2;
scene.add(robot);
document.getElementById('status').textContent = 'URDF Loaded';
loader.parseVisual = true;
loader.parseCollision = false;
// Add AxesHelpers to all meshes
robot.traverse(obj => {
if (obj.isMesh) obj.add(new THREE.AxesHelper(0.05));
});
// Setup pointer events
canvas.addEventListener('pointermove', event => {
mouse.x = (event.clientX / canvas.clientWidth) * 2 - 1;
mouse.y = -(event.clientY / canvas.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(robot.children, true);
if (isDragging && draggedJoint && worldAxis) {
const deltaX = event.clientX - lastX;
lastX = event.clientX;
const angle = deltaX * 0.005;
console.log(worldAxis, angle);
draggedJoint.rotateOnAxis(worldAxis, angle);
}
if (intersects.length > 0) {
const target = intersects[0].object;
// Reset previous highlight
if (
hoveredJoint &&
hoveredJoint.material &&
'emissive' in hoveredJoint.material &&
typeof hoveredJoint.material.emissive.setHex === 'function'
) {
hoveredJoint.material.emissive.setHex(0x000000);
}
hoveredJoint = target;
// Highlight new joint
if (
hoveredJoint.material &&
'emissive' in hoveredJoint.material &&
typeof hoveredJoint.material.emissive.setHex === 'function'
) {
hoveredJoint.material.emissive.setHex(0x333333);
}
} else {
if (
hoveredJoint &&
hoveredJoint.material &&
'emissive' in hoveredJoint.material &&
typeof hoveredJoint.material.emissive.setHex === 'function'
) {
hoveredJoint.material.emissive.setHex(0x000000);
}
hoveredJoint = null;
}
});
canvas.addEventListener('pointerdown', event => {
if (!hoveredJoint) return;
isDragging = true;
lastX = event.clientX;
controls.enabled = false;
draggedJoint = findJointAncestor(hoveredJoint);
if (!draggedJoint) return;
const jointName = draggedJoint.name;
const jointData = robot.joints?.[jointName];
// ✅ Check if axis is a valid THREE.Vector3
if (!(jointData?.axis instanceof THREE.Vector3)) {
console.warn(`Invalid axis for joint "${jointName}":`, jointData?.axis);
return;
}
const urdfAxis = jointData.axis.clone();
if (urdfAxis.lengthSq() === 0) {
console.warn(`Zero-length axis for joint "${jointName}"`);
return;
}
worldAxis = urdfAxis.normalize();
console.log(`Dragging joint "${jointName}" on axis`, worldAxis.toArray());
});
canvas.addEventListener('pointerup', () => {
isDragging = false;
controls.enabled = true;
draggedJoint = null;
worldAxis = null;
});
animate();
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}