You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
8.1 KiB
304 lines
8.1 KiB
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>three.js raycaster - bvh</title>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
<link type="text/css" rel="stylesheet" href="main.css">
|
|
<style>
|
|
body {
|
|
background-color: #eeeeee;
|
|
color: #333;
|
|
}
|
|
|
|
a {
|
|
color: #E91E63;
|
|
text-decoration: underline;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="info">
|
|
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> raycaster - <a href="https://github.com/gkjohnson/three-mesh-bvh" target="_blank" rel="noopener">three-mesh-bvh</a><br/>
|
|
See <a href="https://github.com/gkjohnson/three-mesh-bvh" target="_blank" rel="noopener">main project repository</a> for more information and examples on high performance spatial queries.
|
|
</div>
|
|
|
|
<!-- Import maps polyfill -->
|
|
<!-- Remove this when import maps will be widely supported -->
|
|
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
|
|
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"three": "../build/three.module.js",
|
|
"three/addons/": "./jsm/",
|
|
"three-mesh-bvh": "https://unpkg.com/three-mesh-bvh@^0.5.10/build/index.module.js"
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script type="module">
|
|
|
|
import * as THREE from 'three';
|
|
import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast, MeshBVHVisualizer } from 'three-mesh-bvh';
|
|
import Stats from 'three/addons/libs/stats.module.js';
|
|
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
|
|
|
// Add the extension functions
|
|
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
|
|
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
|
|
THREE.Mesh.prototype.raycast = acceleratedRaycast;
|
|
|
|
let stats;
|
|
let camera, scene, renderer;
|
|
let mesh, helper, bvh;
|
|
let sphereInstance, lineSegments;
|
|
|
|
// reusable variables
|
|
const _raycaster = new THREE.Raycaster();
|
|
const _position = new THREE.Vector3();
|
|
const _quaternion = new THREE.Quaternion();
|
|
const _scale = new THREE.Vector3( 1, 1, 1 );
|
|
const _matrix = new THREE.Matrix4();
|
|
const _axis = new THREE.Vector3();
|
|
const MAX_RAYS = 3000;
|
|
const RAY_COLOR = 0x444444;
|
|
|
|
const params = {
|
|
|
|
count: 150,
|
|
firstHitOnly: true,
|
|
useBVH: true,
|
|
|
|
displayHelper: false,
|
|
helperDepth: 10,
|
|
|
|
};
|
|
|
|
init();
|
|
animate();
|
|
|
|
function init() {
|
|
|
|
// environment
|
|
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 100 );
|
|
camera.position.z = 10;
|
|
|
|
scene = new THREE.Scene();
|
|
scene.background = new THREE.Color( 0xeeeeee );
|
|
|
|
const ambient = new THREE.HemisphereLight( 0xffffff, 0x999999 );
|
|
scene.add( ambient );
|
|
|
|
// renderer
|
|
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
renderer.setPixelRatio( window.devicePixelRatio );
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
document.body.appendChild( renderer.domElement );
|
|
|
|
stats = new Stats();
|
|
document.body.appendChild( stats.dom );
|
|
|
|
// raycast visualizations
|
|
const lineGeometry = new THREE.BufferGeometry();
|
|
lineGeometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( MAX_RAYS * 2 * 3 ), 3 ) );
|
|
lineSegments = new THREE.LineSegments( lineGeometry, new THREE.LineBasicMaterial( {
|
|
color: RAY_COLOR,
|
|
transparent: true,
|
|
opacity: 0.25,
|
|
depthWrite: false
|
|
} ) );
|
|
|
|
sphereInstance = new THREE.InstancedMesh(
|
|
new THREE.SphereGeometry(),
|
|
new THREE.MeshBasicMaterial( { color: RAY_COLOR } ),
|
|
2 * MAX_RAYS
|
|
);
|
|
sphereInstance.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
|
|
sphereInstance.count = 0;
|
|
|
|
scene.add( sphereInstance, lineSegments );
|
|
|
|
// load the bunny
|
|
const loader = new FBXLoader();
|
|
loader.load( 'models/fbx/stanford-bunny.fbx', object => {
|
|
|
|
mesh = object.children[ 0 ];
|
|
|
|
const geometry = mesh.geometry;
|
|
geometry.translate( 0, 0.5 / 0.0075, 0 );
|
|
geometry.computeBoundsTree();
|
|
bvh = geometry.boundsTree;
|
|
|
|
if ( ! params.useBVH ) {
|
|
|
|
geometry.boundsTree = null;
|
|
|
|
}
|
|
|
|
scene.add( mesh );
|
|
mesh.scale.setScalar( 0.0075 );
|
|
|
|
helper = new MeshBVHVisualizer( mesh );
|
|
helper.color.set( 0xE91E63 );
|
|
scene.add( helper );
|
|
|
|
} );
|
|
|
|
const controls = new OrbitControls( camera, renderer.domElement );
|
|
controls.minDistance = 5;
|
|
controls.maxDistance = 75;
|
|
|
|
// set up gui
|
|
const gui = new GUI();
|
|
const rayFolder = gui.addFolder( 'Raycasting' );
|
|
rayFolder.add( params, 'count', 1, MAX_RAYS, 1 );
|
|
rayFolder.add( params, 'firstHitOnly' );
|
|
rayFolder.add( params, 'useBVH' ).onChange( v => {
|
|
|
|
mesh.geometry.boundsTree = v ? bvh : null;
|
|
|
|
} );
|
|
|
|
const helperFolder = gui.addFolder( 'BVH Helper' );
|
|
helperFolder.add( params, 'displayHelper' );
|
|
helperFolder.add( params, 'helperDepth', 1, 20, 1 ).onChange( v => {
|
|
|
|
helper.depth = v;
|
|
helper.update();
|
|
|
|
} );
|
|
|
|
window.addEventListener( 'resize', onWindowResize );
|
|
onWindowResize();
|
|
|
|
initRays();
|
|
|
|
}
|
|
|
|
function initRays() {
|
|
|
|
const position = new THREE.Vector3();
|
|
const quaternion = new THREE.Quaternion();
|
|
const scale = new THREE.Vector3( 1, 1, 1 );
|
|
const matrix = new THREE.Matrix4();
|
|
|
|
for ( let i = 0; i < MAX_RAYS * 2; i ++ ) {
|
|
|
|
position.randomDirection().multiplyScalar( 3.75 );
|
|
matrix.compose( position, quaternion, scale );
|
|
sphereInstance.setMatrixAt( i, matrix );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function updateRays() {
|
|
|
|
if ( ! mesh ) return;
|
|
|
|
_raycaster.firstHitOnly = params.firstHitOnly;
|
|
const rayCount = params.count;
|
|
|
|
let lineNum = 0;
|
|
for ( let i = 0; i < rayCount; i ++ ) {
|
|
|
|
// get the current ray origin
|
|
sphereInstance.getMatrixAt( i * 2, _matrix );
|
|
_matrix.decompose( _position, _quaternion, _scale );
|
|
|
|
// rotate it about the origin
|
|
const offset = 1e-4 * window.performance.now();
|
|
_axis.set(
|
|
Math.sin( i * 100 + offset ),
|
|
Math.cos( - i * 10 + offset ),
|
|
Math.sin( i * 1 + offset ),
|
|
).normalize();
|
|
_position.applyAxisAngle( _axis, 0.001 );
|
|
|
|
// update the position
|
|
_scale.setScalar( 0.02 );
|
|
_matrix.compose( _position, _quaternion, _scale );
|
|
sphereInstance.setMatrixAt( i * 2, _matrix );
|
|
|
|
// raycast
|
|
_raycaster.ray.origin.copy( _position );
|
|
_raycaster.ray.direction.copy( _position ).multiplyScalar( - 1 ).normalize();
|
|
|
|
// update hits points and lines
|
|
const hits = _raycaster.intersectObject( mesh );
|
|
if ( hits.length !== 0 ) {
|
|
|
|
const hit = hits[ 0 ];
|
|
const point = hit.point;
|
|
_scale.setScalar( 0.01 );
|
|
_matrix.compose( point, _quaternion, _scale );
|
|
sphereInstance.setMatrixAt( i * 2 + 1, _matrix );
|
|
|
|
lineSegments.geometry.attributes.position.setXYZ( lineNum ++, _position.x, _position.y, _position.z );
|
|
lineSegments.geometry.attributes.position.setXYZ( lineNum ++, point.x, point.y, point.z );
|
|
|
|
} else {
|
|
|
|
sphereInstance.setMatrixAt( i * 2 + 1, _matrix );
|
|
lineSegments.geometry.attributes.position.setXYZ( lineNum ++, _position.x, _position.y, _position.z );
|
|
lineSegments.geometry.attributes.position.setXYZ( lineNum ++, 0, 0, 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sphereInstance.count = rayCount * 2;
|
|
sphereInstance.instanceMatrix.needsUpdate = true;
|
|
|
|
lineSegments.geometry.setDrawRange( 0, lineNum );
|
|
lineSegments.geometry.attributes.position.needsUpdate = true;
|
|
|
|
}
|
|
|
|
function onWindowResize() {
|
|
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
}
|
|
|
|
function animate() {
|
|
|
|
requestAnimationFrame( animate );
|
|
|
|
render();
|
|
stats.update();
|
|
|
|
}
|
|
|
|
function render() {
|
|
|
|
if ( helper ) {
|
|
|
|
helper.visible = params.displayHelper;
|
|
|
|
}
|
|
|
|
if ( mesh ) {
|
|
|
|
mesh.rotation.y += 0.002;
|
|
mesh.updateMatrixWorld();
|
|
|
|
}
|
|
|
|
updateRays();
|
|
|
|
renderer.render( scene, camera );
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
|