Raycasting — ключевой инструмент для взаимодействия с 3D-сценой в физическом движке Ammo.js. Он позволяет определить пересечение луча с объектами сцены, что необходимо для выбора объектов мышью, проверки коллизий или симуляции трассировки пуль и лазеров. В основе лежит преобразование 2D-координат экрана в 3D-пространство и создание физического луча, который может взаимодействовать с телами в мире Ammo.js.
В WebGL и Three.js экранные координаты мыши обычно получаются в пикселях относительно окна:
const mouseX = event.clientX;
const mouseY = event.clientY;
Для raycasting необходимо перевести их в нормализованные координаты устройства (NDC, Normalized Device Coordinates), где диапазон X и Y находится в пределах [-1, 1]:
const ndcX = (mouseX / window.innerWidth) * 2 - 1;
const ndcY = -(mouseY / window.innerHeight) * 2 + 1;
Особенности:
Для генерации луча в 3D-пространстве используется матрица проекции
камеры. В Three.js можно воспользоваться THREE.Vector3 и
методом unproject:
const vector = new THREE.Vector3(ndcX, ndcY, 0.5);
vector.unproject(camera); // Преобразование в мировые координаты
const origin = camera.position.clone();
const direction = vector.sub(origin).normalize();
Здесь origin — точка начала луча (обычно позиция
камеры), direction — нормализованный вектор
направления.
Ammo.js использует собственные структуры для raycasting:
btVector3 для векторов и
btCollisionWorld::ClosestRayResultCallback для вычисления
пересечений.
const from = new Ammo.btVector3(origin.x, origin.y, origin.z);
const toPoint = new THREE.Vector3();
toPoint.copy(origin).add(direction.clone().multiplyScalar(1000)); // длина луча
const to = new Ammo.btVector3(toPoint.x, toPoint.y, toPoint.z);
const rayCallback = new Ammo.ClosestRayResultCallback(from, to);
physicsWorld.rayTest(from, to, rayCallback);
Пояснение ключевых моментов:
from и to задают начало и конец луча в
мировых координатах.ClosestRayResultCallback возвращает первый объект, с
которым пересекается луч.AllHitsRayResultCallback.После выполнения rayTest можно извлечь данные о
пересечении:
if (rayCallback.hasHit()) {
const collisionPoint = rayCallback.get_hitPointWorld();
const collisionNormal = rayCallback.get_hitNormalWorld();
const hitPos = {
x: collisionPoint.x(),
y: collisionPoint.y(),
z: collisionPoint.z()
};
const hitNormal = {
x: collisionNormal.x(),
y: collisionNormal.y(),
z: collisionNormal.z()
};
}
Особенности:
get_hitPointWorld() возвращает точку пересечения в
мировых координатах.get_hitNormalWorld() — нормаль поверхности, с которой
произошло пересечение.Длина луча Локальная длина луча влияет на точность и производительность. Рекомендуется выбирать разумный диапазон (например, несколько сотен единиц сцены), чтобы избежать ненужных проверок на дальних объектах.
Фильтрация объектов Ammo.js позволяет использовать маски коллизий для исключения нежелательных объектов:
rayCallback.set_m_collisionFilterMask(mask);
rayCallback.set_m_collisionFilterGroup(group);Преобразование между библиотеками Взаимодействие
Three.js и Ammo.js требует точного конвертирования координат:
THREE.Vector3 → btVector3 и наоборот.
Все пересечения Если необходимо знать не только
ближайший объект, можно использовать
AllHitsRayResultCallback и получить массив точек, нормалей
и объектов пересечения.
window.addEventListener('click', (event) => {
const ndcX = (event.clientX / window.innerWidth) * 2 - 1;
const ndcY = -(event.clientY / window.innerHeight) * 2 + 1;
const vector = new THREE.Vector3(ndcX, ndcY, 0.5).unproject(camera);
const origin = camera.position.clone();
const direction = vector.sub(origin).normalize();
const from = new Ammo.btVector3(origin.x, origin.y, origin.z);
const toPoint = origin.clone().add(direction.multiplyScalar(1000));
const to = new Ammo.btVector3(toPoint.x, toPoint.y, toPoint.z);
const rayCallback = new Ammo.ClosestRayResultCallback(from, to);
physicsWorld.rayTest(from, to, rayCallback);
if (rayCallback.hasHit()) {
const collisionPoint = rayCallback.get_hitPointWorld();
console.log("Hit at:", collisionPoint.x(), collisionPoint.y(), collisionPoint.z());
}
});
btVector3) для
Ammo.js.rayTest с использованием
ClosestRayResultCallback или
AllHitsRayResultCallback.Этот процесс является фундаментальным для интерактивных приложений с физикой, обеспечивая точное попадание луча по 3D-объектам сцены и корректное взаимодействие с физическим миром.