|
@@ -0,0 +1,302 @@
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
+<html lang="en">
|
|
|
|
+ <head>
|
|
|
|
+ <title>three-mesh-bvh - raycasting - 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 {
|
|
|
|
+ color: #333;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ a {
|
|
|
|
+ color: #E91E63;
|
|
|
|
+ text-decoration: underline;
|
|
|
|
+ }
|
|
|
|
+ </style>
|
|
|
|
+ </head>
|
|
|
|
+
|
|
|
|
+ <body>
|
|
|
|
+ <div id="info">
|
|
|
|
+ <a href="https://github.com/gkjohnson/three-mesh-bvh" target="_blank" rel="noopener">three-mesh-bvh</a> accelerated raycasting demo<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/[email protected]/dist/es-module-shims.js"></script>
|
|
|
|
+
|
|
|
|
+ <script type="importmap">
|
|
|
|
+ {
|
|
|
|
+ "imports": {
|
|
|
|
+ "three": "../build/three.module.js",
|
|
|
|
+ "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 './jsm/libs/stats.module.js';
|
|
|
|
+ import { FBXLoader } from './jsm/loaders/FBXLoader.js';
|
|
|
|
+ import { OrbitControls } from './jsm/controls/OrbitControls.js';
|
|
|
|
+ import { GUI } from './jsm/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, controls;
|
|
|
|
+ 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.SphereBufferGeometry(),
|
|
|
|
+ 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 );
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ controls = new OrbitControls( camera, renderer.domElement );
|
|
|
|
+
|
|
|
|
+ // 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() {
|
|
|
|
+
|
|
|
|
+ const hx = window.innerWidth / 2;
|
|
|
|
+ const hy = window.innerHeight / 2;
|
|
|
|
+
|
|
|
|
+ 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>
|