Răsfoiți Sursa

make InstancedMesh support raycast
and add webgl_instancing_raycast demo

webglzhang 5 ani în urmă
părinte
comite
05708fc8f7
2 a modificat fișierele cu 452 adăugiri și 3 ștergeri
  1. 151 0
      examples/webgl_instancing_raycast.html
  2. 301 3
      src/objects/InstancedMesh.js

+ 151 - 0
examples/webgl_instancing_raycast.html

@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<title>three.js webgl - instancing - raycast </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">
+</head>
+<body>
+<script type="module">
+
+	import * as THREE from '../build/three.module.js';
+
+	import Stats from './jsm/libs/stats.module.js';
+	import {GUI} from './jsm/libs/dat.gui.module.js';
+	import {OrbitControls} from "./jsm/controls/OrbitControls.js";
+
+	var camera, scene, renderer, stats;
+
+	var mesh, geometry;
+	var amount = parseInt(window.location.search.substr(1)) || 10;
+	var count = Math.pow(amount, 3);
+	var object = new THREE.Object3D();
+
+	var intersection;
+	var raycaster = new THREE.Raycaster();
+	var mouse = new THREE.Vector2();
+
+	var orbitControls;
+
+	var rotationOffset=0.1;
+
+	init();
+	animate();
+
+	function init() {
+
+		camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
+		camera.position.set(amount , amount , amount );
+		camera.lookAt(0, 0, 0);
+
+
+		scene = new THREE.Scene();
+
+		geometry = new THREE.BoxBufferGeometry(1, 1, 1);
+		geometry.computeVertexNormals();
+		geometry.scale(0.5, 0.5, 0.5);
+
+		var material = new THREE.MeshNormalMaterial();
+
+		mesh = new THREE.InstancedMesh(geometry, material, count);
+
+		var i = 0;
+		var offset = amount / 2;
+
+		for (var x = 0; x < amount; x++) {
+
+			for (var y = 0; y < amount; y++) {
+
+				for (var z = 0; z < amount; z++) {
+
+					object.position.set(offset - x, offset - y, offset - z);
+
+					object.updateMatrix();
+
+					mesh.setMatrixAt(i++, object.matrix);
+
+				}
+
+			}
+
+		}
+
+		scene.add(mesh);
+
+
+		var gui = new GUI();
+		gui.add(mesh, 'count', 0, count);
+
+		renderer = new THREE.WebGLRenderer({antialias: true});
+		renderer.setPixelRatio(window.devicePixelRatio);
+		renderer.setSize(window.innerWidth, window.innerHeight);
+
+		document.body.appendChild(renderer.domElement);
+
+		orbitControls=new OrbitControls(camera,renderer.domElement);
+
+		stats = new Stats();
+		document.body.appendChild(stats.dom);
+
+		window.addEventListener('resize', onWindowResize, false);
+		document.addEventListener( 'mousemove', onMouseMove, false );
+
+	}
+
+	function onWindowResize() {
+
+		camera.aspect = window.innerWidth / window.innerHeight;
+		camera.updateProjectionMatrix();
+
+		renderer.setSize(window.innerWidth, window.innerHeight);
+
+	}
+
+
+	function animate() {
+
+		requestAnimationFrame(animate);
+
+		render();
+
+	}
+
+
+	function render() {
+
+		camera.updateMatrixWorld();
+
+		raycaster.setFromCamera(mouse, camera);
+
+		intersection = raycaster.intersectObjects( scene.children );
+
+
+		if (intersection.length > 0) {
+
+			var rotationMatrix=new THREE.Matrix4().makeRotationY(rotationOffset) ;
+			var instanceMatrix=mesh.getMatrixAt(intersection[0].instanceID);
+			var matrix=new THREE.Matrix4().multiplyMatrices(instanceMatrix,rotationMatrix);
+
+			mesh.setMatrixAt(intersection[0].instanceID,matrix);
+			mesh.instanceMatrix.needsUpdate = true;
+		}
+
+		renderer.render(scene, camera);
+
+		stats.update();
+	}
+
+	function onMouseMove(event) {
+
+		event.preventDefault();
+
+		mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
+		mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
+
+	}
+
+</script>
+
+</body>
+</html>

+ 301 - 3
src/objects/InstancedMesh.js

@@ -1,9 +1,31 @@
 /**
  * @author mrdoob / http://mrdoob.com/
  */
-
 import { BufferAttribute } from '../core/BufferAttribute.js';
 import { Mesh } from './Mesh.js';
+import { Vector3 } from '../math/Vector3.js';
+import { Vector2 } from '../math/Vector2.js';
+import { Sphere } from '../math/Sphere.js';
+import { Ray } from '../math/Ray.js';
+import { Matrix4 } from '../math/Matrix4.js';
+import { Triangle } from '../math/Triangle.js';
+import { Face3 } from '../core/Face3.js';
+import { DoubleSide, BackSide } from '../constants.js';
+
+var _inverseMatrix = new Matrix4();
+var _ray = new Ray();
+var _sphere = new Sphere();
+
+var _vA = new Vector3();
+var _vB = new Vector3();
+var _vC = new Vector3();
+
+var _uvA = new Vector2();
+var _uvB = new Vector2();
+var _uvC = new Vector2();
+
+var _intersectionPoint = new Vector3();
+var _intersectionPointWorld = new Vector3();
 
 function InstancedMesh( geometry, material, count ) {
 
@@ -21,7 +43,209 @@ InstancedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
 
 	isInstancedMesh: true,
 
-	raycast: function () {},
+	raycast: function ( raycaster, intersects ) {
+
+		var geometry = this.geometry;
+		var material = this.material;
+		var matrixWorld = this.matrixWorld;
+
+		if ( material === undefined ) return;
+
+		for ( var instanceID = 0; instanceID < this.count; instanceID ++ ) {
+
+			//Calculate the world matrix for each instance
+
+			var instanceMatrixWorld = new Matrix4();
+
+			var instanceMatrix = this.getMatrixAt( instanceID );
+
+			instanceMatrixWorld.multiplyMatrices( matrixWorld, instanceMatrix );
+
+			// Checking boundingSphere distance to ray
+
+			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+			_sphere.copy( geometry.boundingSphere );
+			_sphere.applyMatrix4( instanceMatrixWorld );
+
+			if ( raycaster.ray.intersectsSphere( _sphere ) === false ) continue;
+
+			//Transform the ray into the local space of the model
+
+			_inverseMatrix.getInverse( instanceMatrixWorld );
+			_ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
+
+			// Check boundingBox before continuing
+
+			if ( geometry.boundingBox !== null ) {
+
+				if ( _ray.intersectsBox( geometry.boundingBox ) === false ) continue;
+
+			}
+
+			var intersection;
+
+			if ( geometry.isBufferGeometry ) {
+
+				var a, b, c;
+				var index = geometry.index;
+				var position = geometry.attributes.position;
+				var uv = geometry.attributes.uv;
+				var uv2 = geometry.attributes.uv2;
+				var groups = geometry.groups;
+				var drawRange = geometry.drawRange;
+				var i, j, il, jl;
+				var group, groupMaterial;
+				var start, end;
+
+				if ( index !== null ) {
+
+					// indexed buffer geometry
+
+					if ( Array.isArray( material ) ) {
+
+						for ( i = 0, il = groups.length; i < il; i ++ ) {
+
+							group = groups[ i ];
+							groupMaterial = material[ group.materialIndex ];
+
+							start = Math.max( group.start, drawRange.start );
+							end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
+
+							for ( j = start, jl = end; j < jl; j += 3 ) {
+
+								a = index.getX( j );
+								b = index.getX( j + 1 );
+								c = index.getX( j + 2 );
+
+								intersection = checkBufferGeometryIntersection( this, instanceMatrixWorld, groupMaterial, raycaster, _ray, position, uv, uv2, a, b, c );
+
+								if ( intersection ) {
+
+									intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics
+									intersection.face.materialIndex = group.materialIndex;
+
+									intersection.instanceID = instanceID;
+
+									intersects.push( intersection );
+
+								}
+
+							}
+
+						}
+
+					} else {
+
+						start = Math.max( 0, drawRange.start );
+						end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
+
+						for ( i = start, il = end; i < il; i += 3 ) {
+
+							a = index.getX( i );
+							b = index.getX( i + 1 );
+							c = index.getX( i + 2 );
+
+							intersection = checkBufferGeometryIntersection( this, instanceMatrixWorld, material, raycaster, _ray, position, uv, uv2, a, b, c );
+
+							if ( intersection ) {
+
+								intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics
+
+								intersection.instanceID = instanceID;
+
+								intersects.push( intersection );
+
+							}
+
+						}
+
+					}
+
+				} else if ( position !== undefined ) {
+
+					// non-indexed buffer geometry
+
+					if ( Array.isArray( material ) ) {
+
+						for ( i = 0, il = groups.length; i < il; i ++ ) {
+
+							group = groups[ i ];
+							groupMaterial = material[ group.materialIndex ];
+
+							start = Math.max( group.start, drawRange.start );
+							end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
+
+							for ( j = start, jl = end; j < jl; j += 3 ) {
+
+								a = j;
+								b = j + 1;
+								c = j + 2;
+
+								intersection = checkBufferGeometryIntersection( this, instanceMatrixWorld, groupMaterial, raycaster, _ray, position, uv, uv2, a, b, c );
+
+								if ( intersection ) {
+
+									intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics
+									intersection.face.materialIndex = group.materialIndex;
+
+									intersection.instanceID = instanceID;
+
+									intersects.push( intersection );
+
+								}
+
+							}
+
+						}
+
+					} else {
+
+						start = Math.max( 0, drawRange.start );
+						end = Math.min( position.count, ( drawRange.start + drawRange.count ) );
+
+						for ( i = start, il = end; i < il; i += 3 ) {
+
+							a = i;
+							b = i + 1;
+							c = i + 2;
+
+							intersection = checkBufferGeometryIntersection( this, instanceMatrixWorld, material, raycaster, _ray, position, uv, uv2, a, b, c );
+
+							if ( intersection ) {
+
+								intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics
+
+								intersection.instanceID = instanceID;
+
+								intersects.push( intersection );
+
+							}
+
+						}
+
+					}
+
+				}
+
+			}
+
+
+
+
+		}
+
+	},
+
+	getMatrixAt: function ( index ) {
+
+		var matrix = new Matrix4();
+
+		matrix.fromArray( this.instanceMatrix.array, index * 16 );
+
+		return matrix;
+
+	},
 
 	setMatrixAt: function ( index, matrix ) {
 
@@ -29,8 +253,82 @@ InstancedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
 
 	},
 
-	updateMorphTargets: function () {}
+	updateMorphTargets: function () {
+
+	}
 
 } );
 
+function checkIntersection( object, matrixWorld, material, raycaster, ray, pA, pB, pC, point ) {
+
+	var intersect;
+
+	if ( material.side === BackSide ) {
+
+		intersect = ray.intersectTriangle( pC, pB, pA, true, point );
+
+	} else {
+
+		intersect = ray.intersectTriangle( pA, pB, pC, material.side !== DoubleSide, point );
+
+	}
+
+	if ( intersect === null ) return null;
+
+	_intersectionPointWorld.copy( point );
+	_intersectionPointWorld.applyMatrix4( matrixWorld );
+
+	var distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld );
+
+	if ( distance < raycaster.near || distance > raycaster.far ) return null;
+
+	return {
+		distance: distance,
+		point: _intersectionPointWorld.clone(),
+		object: object
+	};
+
+}
+
+function checkBufferGeometryIntersection( object, matrixWorld, material, raycaster, ray, position, uv, uv2, a, b, c ) {
+
+	_vA.fromBufferAttribute( position, a );
+	_vB.fromBufferAttribute( position, b );
+	_vC.fromBufferAttribute( position, c );
+
+	var intersection = checkIntersection( object, matrixWorld, material, raycaster, ray, _vA, _vB, _vC, _intersectionPoint );
+
+	if ( intersection ) {
+
+		if ( uv ) {
+
+			_uvA.fromBufferAttribute( uv, a );
+			_uvB.fromBufferAttribute( uv, b );
+			_uvC.fromBufferAttribute( uv, c );
+
+			intersection.uv = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
+
+		}
+
+		if ( uv2 ) {
+
+			_uvA.fromBufferAttribute( uv2, a );
+			_uvB.fromBufferAttribute( uv2, b );
+			_uvC.fromBufferAttribute( uv2, c );
+
+			intersection.uv2 = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
+
+		}
+
+		var face = new Face3( a, b, c );
+		Triangle.getNormal( _vA, _vB, _vC, face.normal );
+
+		intersection.face = face;
+
+	}
+
+	return intersection;
+
+}
+
 export { InstancedMesh };