I’m trying to setup a basic Raycast for the ThreeJS AR Hittest example - three.js ar - hit test
The raycast is logged when it intersects something.
if ( intersects.length > 0 ) {
console.log("intersection successful");
}
I’ve been experimenting with getting the correct coordinates for the raycast from the current camera position to the current reticle position each frame.
As the camera and reticle move in AR, it seems that using raycast.setFromCamera() has trouble updating the camera position and continues to use the starting camera location.
So far I’ve found I can do this by using their matrix and matrix world positions each frame. I’ve used threejs lines to confirm this when testing.
const reticleSelect = new THREE.Vector3();
const cameraSelect = new THREE.Vector3();
reticleSelect.setFromMatrixPosition( reticle.matrix );
cameraSelect.setFromMatrixPosition( camera.matrixWorld );
//Below shows that cameraSelect and reticleSelect provide the correct coordinates from camera to reticle.
const line = new THREE.Line( new THREE.BufferGeometry().setFromPoints( [cameraSelect, reticleSelect] ), new THREE.LineBasicMaterial );
scene.add( line );
However the intersection log is never called when I use these coordinates in a raycast.set(), despite an intersection appearing to happen with geometry placed in the AR scene.
According to my lines there should be an intersection and the log should appear, I haven’t been able to find anything online either after some searching. Any advice?
full code below, code lines 114 and 115 contains the line geometry. The raycast function is called every frame.
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js ar - raycast test</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> ar - hit test<br/>Enable chrome://flags/#webxr-ar-module<br/>Enable chrome://flags/#webxr-hit-test<br/>(Chrome Android 81+)
</div>
<script type="module">
import three from 'https://cdn.skypack.dev/three';
import * as THREE from 'https://cdn.skypack.dev/three';
import { ARButton } from 'https://cdn.skypack.dev/three/examples/jsm/webxr/ARButton.js';
console.log(THREE);
var container;
var camera, scene, renderer;
var controller;
var reticle;
var hitTestSource = null;
var hitTestSourceRequested = false;
const reticleSelect = new THREE.Vector3();
const cameraSelect = new THREE.Vector3();
const raycaster = new THREE.Raycaster();
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 20 );
var light = new THREE.HemisphereLight( 0xffffff, 0xbbbbff, 1 );
light.position.set( 0.5, 1, 0.25 );
scene.add( light );
//
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.xr.enabled = true;
container.appendChild( renderer.domElement );
//
document.body.appendChild( ARButton.createButton( renderer, { requiredFeatures: [ 'hit-test' ] } ) );
//
var geometry = new THREE.CylinderBufferGeometry( 0.1, 0.1, 0.2, 32 ).translate( 0, 0.1, 0 );
function onSelect() {
if ( reticle.visible ) {
var material = new THREE.MeshPhongMaterial( { color: 0xffffff * Math.random() } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.setFromMatrixPosition( reticle.matrix );
mesh.scale.y = Math.random() * 2 + 1;
scene.add( mesh );
}
}
controller = renderer.xr.getController( 0 );
controller.addEventListener( 'select', onSelect );
scene.add( controller );
reticle = new THREE.Mesh(
new THREE.RingBufferGeometry( 0.15, 0.2, 32 ).rotateX( - Math.PI / 2 ),
new THREE.MeshBasicMaterial()
);
reticle.matrixAutoUpdate = false;
reticle.visible = false;
scene.add( reticle );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function castRayCenter() {
reticleSelect.setFromMatrixPosition( reticle.matrix );
cameraSelect.setFromMatrixPosition( camera.matrixWorld );
raycaster.set(cameraSelect, reticleSelect );
const intersects = raycaster.intersectObjects( scene.children );
//Below shows that cameraSelect and reticleSelect provide the correct coordinates from camera to reticle.
//const line = new THREE.Line( new THREE.BufferGeometry().setFromPoints( [cameraSelect, reticleSelect] ), new THREE.LineBasicMaterial );
//scene.add( line );
if ( intersects.length > 0 ) {
console.log("intersection successful");
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
//
function animate() {
renderer.setAnimationLoop( render );
}
function render( timestamp, frame ) {
if ( frame ) {
var referenceSpace = renderer.xr.getReferenceSpace();
var session = renderer.xr.getSession();
if ( hitTestSourceRequested === false ) {
session.requestReferenceSpace( 'viewer' ).then( function ( referenceSpace ) {
session.requestHitTestSource( { space: referenceSpace } ).then( function ( source ) {
hitTestSource = source;
} );
} );
session.addEventListener( 'end', function () {
hitTestSourceRequested = false;
hitTestSource = null;
} );
hitTestSourceRequested = true;
}
if ( hitTestSource ) {
var hitTestResults = frame.getHitTestResults( hitTestSource );
if ( hitTestResults.length ) {
var hit = hitTestResults[ 0 ];
reticle.visible = true;
reticle.matrix.fromArray( hit.getPose( referenceSpace ).transform.matrix );
} else {
reticle.visible = false;
}
}
castRayCenter();
}
renderer.render( scene, camera );
}
</script>
</body>
</html>