Quellcode durchsuchen

Line2 world units raycaster (#23358)

* Try brute force...

* Add example and clean-up code

* Remove comment

* Applying PR comments, bug fix, creating new example for raycasting

* adding new example to the tags

* Add example icon and undo build/ changes

* Revert build files

* Checkout build/ from updated upstream

* WIP refactor to split logic

* Bug fixes and cleanup

* Fix screenshot

* PR notes

* Cleaning up

* More cleanup

* Bug fixes

Co-authored-by: ariel-resonai <[email protected]>
Shai Daniel Ghelberg vor 3 Jahren
Ursprung
Commit
aa01ab388d

+ 1 - 0
examples/files.json

@@ -67,6 +67,7 @@
 		"webgl_lines_colors",
 		"webgl_lines_dashed",
 		"webgl_lines_fat",
+		"webgl_lines_fat_raycasting",
 		"webgl_lines_fat_wireframe",
 		"webgl_lines_sphere",
 		"webgl_loader_3dm",

+ 1 - 1
examples/jsm/lines/LineMaterial.js

@@ -219,7 +219,7 @@ ShaderLib[ 'line' ] = {
 				vec4 clip = projectionMatrix * worldPos;
 
 				// shift the depth of the projected points so the line
-				// segements overlap neatly
+				// segments overlap neatly
 				vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd;
 				clip.z = clipPose.z * clip.w;
 

+ 208 - 143
examples/jsm/lines/LineSegments2.js

@@ -29,17 +29,19 @@ const _box = new Box3();
 const _sphere = new Sphere();
 const _clipToWorldVector = new Vector4();
 
+let _ray, _instanceStart, _instanceEnd, _lineWidth;
+
 // Returns the margin required to expand by in world space given the distance from the camera,
 // line width, resolution, and camera projection
-function getWorldSpaceHalfWidth( camera, distance, lineWidth, resolution ) {
+function getWorldSpaceHalfWidth( camera, distance, resolution ) {
 
 	// transform into clip space, adjust the x and y values by the pixel width offset, then
 	// transform back into world space to get world offset. Note clip space is [-1, 1] so full
 	// width does not need to be halved.
 	_clipToWorldVector.set( 0, 0, - distance, 1.0 ).applyMatrix4( camera.projectionMatrix );
 	_clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w );
-	_clipToWorldVector.x = lineWidth / resolution.width;
-	_clipToWorldVector.y = lineWidth / resolution.height;
+	_clipToWorldVector.x = _lineWidth / resolution.width;
+	_clipToWorldVector.y = _lineWidth / resolution.height;
 	_clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse );
 	_clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w );
 
@@ -47,6 +49,170 @@ function getWorldSpaceHalfWidth( camera, distance, lineWidth, resolution ) {
 
 }
 
+function raycastWorldUnits( lineSegments, intersects ) {
+
+	for ( let i = 0, l = _instanceStart.count; i < l; i ++ ) {
+
+		_line.start.fromBufferAttribute( _instanceStart, i );
+		_line.end.fromBufferAttribute( _instanceEnd, i );
+
+		const pointOnLine = new Vector3();
+		const point = new Vector3();
+
+		_ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine );
+		const isInside = point.distanceTo( pointOnLine ) < _lineWidth * 0.5;
+
+		if ( isInside ) {
+
+			intersects.push( {
+				point,
+				pointOnLine,
+				distance: _ray.origin.distanceTo( point ),
+				object: lineSegments,
+				face: null,
+				faceIndex: i,
+				uv: null,
+				uv2: null,
+			} );
+
+		}
+
+	}
+
+}
+
+function raycastScreenSpace( lineSegments, camera, intersects ) {
+
+	const projectionMatrix = camera.projectionMatrix;
+	const material = lineSegments.material;
+	const resolution = material.resolution;
+	const matrixWorld = lineSegments.matrixWorld;
+
+	const geometry = lineSegments.geometry;
+	const instanceStart = geometry.attributes.instanceStart;
+	const instanceEnd = geometry.attributes.instanceEnd;
+
+	const near = - camera.near;
+
+	//
+
+	// pick a point 1 unit out along the ray to avoid the ray origin
+	// sitting at the camera origin which will cause "w" to be 0 when
+	// applying the projection matrix.
+	_ray.at( 1, _ssOrigin );
+
+	// ndc space [ - 1.0, 1.0 ]
+	_ssOrigin.w = 1;
+	_ssOrigin.applyMatrix4( camera.matrixWorldInverse );
+	_ssOrigin.applyMatrix4( projectionMatrix );
+	_ssOrigin.multiplyScalar( 1 / _ssOrigin.w );
+
+	// screen space
+	_ssOrigin.x *= resolution.x / 2;
+	_ssOrigin.y *= resolution.y / 2;
+	_ssOrigin.z = 0;
+
+	_ssOrigin3.copy( _ssOrigin );
+
+	_mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
+
+	for ( let i = 0, l = instanceStart.count; i < l; i ++ ) {
+
+		_start4.fromBufferAttribute( instanceStart, i );
+		_end4.fromBufferAttribute( instanceEnd, i );
+
+		_start4.w = 1;
+		_end4.w = 1;
+
+		// camera space
+		_start4.applyMatrix4( _mvMatrix );
+		_end4.applyMatrix4( _mvMatrix );
+
+		// skip the segment if it's entirely behind the camera
+		const isBehindCameraNear = _start4.z > near && _end4.z > near;
+		if ( isBehindCameraNear ) {
+
+			continue;
+
+		}
+
+		// trim the segment if it extends behind camera near
+		if ( _start4.z > near ) {
+
+			const deltaDist = _start4.z - _end4.z;
+			const t = ( _start4.z - near ) / deltaDist;
+			_start4.lerp( _end4, t );
+
+		} else if ( _end4.z > near ) {
+
+			const deltaDist = _end4.z - _start4.z;
+			const t = ( _end4.z - near ) / deltaDist;
+			_end4.lerp( _start4, t );
+
+		}
+
+		// clip space
+		_start4.applyMatrix4( projectionMatrix );
+		_end4.applyMatrix4( projectionMatrix );
+
+		// ndc space [ - 1.0, 1.0 ]
+		_start4.multiplyScalar( 1 / _start4.w );
+		_end4.multiplyScalar( 1 / _end4.w );
+
+		// screen space
+		_start4.x *= resolution.x / 2;
+		_start4.y *= resolution.y / 2;
+
+		_end4.x *= resolution.x / 2;
+		_end4.y *= resolution.y / 2;
+
+		// create 2d segment
+		_line.start.copy( _start4 );
+		_line.start.z = 0;
+
+		_line.end.copy( _end4 );
+		_line.end.z = 0;
+
+		// get closest point on ray to segment
+		const param = _line.closestPointToPointParameter( _ssOrigin3, true );
+		_line.at( param, _closestPoint );
+
+		// check if the intersection point is within clip space
+		const zPos = MathUtils.lerp( _start4.z, _end4.z, param );
+		const isInClipSpace = zPos >= - 1 && zPos <= 1;
+
+		const isInside = _ssOrigin3.distanceTo( _closestPoint ) < _lineWidth * 0.5;
+
+		if ( isInClipSpace && isInside ) {
+
+			_line.start.fromBufferAttribute( instanceStart, i );
+			_line.end.fromBufferAttribute( instanceEnd, i );
+
+			_line.start.applyMatrix4( matrixWorld );
+			_line.end.applyMatrix4( matrixWorld );
+
+			const pointOnLine = new Vector3();
+			const point = new Vector3();
+
+			_ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine );
+
+			intersects.push( {
+				point: point,
+				pointOnLine: pointOnLine,
+				distance: _ray.origin.distanceTo( point ),
+				object: lineSegments,
+				face: null,
+				faceIndex: i,
+				uv: null,
+				uv2: null,
+			} );
+
+		}
+
+	}
+
+}
+
 class LineSegments2 extends Mesh {
 
 	constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) {
@@ -57,7 +223,7 @@ class LineSegments2 extends Mesh {
 
 	}
 
-	// for backwards-compatability, but could be a method of LineSegmentsGeometry...
+	// for backwards-compatibility, but could be a method of LineSegmentsGeometry...
 
 	computeLineDistances() {
 
@@ -88,31 +254,27 @@ class LineSegments2 extends Mesh {
 
 	raycast( raycaster, intersects ) {
 
-		if ( raycaster.camera === null ) {
+		const worldUnits = this.material.worldUnits;
+		const camera = raycaster.camera;
+
+		if ( camera === null && ! worldUnits ) {
 
-			console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );
+			console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2 while worldUnits is set to false.' );
 
 		}
 
 		const threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0;
 
-		const ray = raycaster.ray;
-		const camera = raycaster.camera;
-		const projectionMatrix = camera.projectionMatrix;
+		_ray = raycaster.ray;
 
 		const matrixWorld = this.matrixWorld;
 		const geometry = this.geometry;
 		const material = this.material;
-		const resolution = material.resolution;
-		const lineWidth = material.linewidth + threshold;
 
-		const instanceStart = geometry.attributes.instanceStart;
-		const instanceEnd = geometry.attributes.instanceEnd;
+		_lineWidth = material.linewidth + threshold;
 
-		// camera forward is negative
-		const near = - camera.near;
-
-		//
+		_instanceStart = geometry.attributes.instanceStart;
+		_instanceEnd = geometry.attributes.instanceEnd;
 
 		// check if we intersect the sphere bounds
 		if ( geometry.boundingSphere === null ) {
@@ -122,162 +284,65 @@ class LineSegments2 extends Mesh {
 		}
 
 		_sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld );
-		const distanceToSphere = Math.max( camera.near, _sphere.distanceToPoint( ray.origin ) );
 
 		// increase the sphere bounds by the worst case line screen space width
-		const sphereMargin = getWorldSpaceHalfWidth( camera, distanceToSphere, lineWidth, resolution );
-		_sphere.radius += sphereMargin;
+		let sphereMargin;
+		if ( worldUnits ) {
 
-		if ( raycaster.ray.intersectsSphere( _sphere ) === false ) {
+			sphereMargin = _lineWidth * 0.5;
 
-			return;
+		} else {
 
-		}
-
-		//
-
-		// check if we intersect the box bounds
-		if ( geometry.boundingBox === null ) {
-
-			geometry.computeBoundingBox();
+			const distanceToSphere = Math.max( camera.near, _sphere.distanceToPoint( _ray.origin ) );
+			sphereMargin = getWorldSpaceHalfWidth( camera, distanceToSphere, material.resolution );
 
 		}
 
-		_box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld );
-		const distanceToBox = Math.max( camera.near, _box.distanceToPoint( ray.origin ) );
-
-		// increase the box bounds by the worst case line screen space width
-		const boxMargin = getWorldSpaceHalfWidth( camera, distanceToBox, lineWidth, resolution );
-		_box.max.x += boxMargin;
-		_box.max.y += boxMargin;
-		_box.max.z += boxMargin;
-		_box.min.x -= boxMargin;
-		_box.min.y -= boxMargin;
-		_box.min.z -= boxMargin;
+		_sphere.radius += sphereMargin;
 
-		if ( raycaster.ray.intersectsBox( _box ) === false ) {
+		if ( _ray.intersectsSphere( _sphere ) === false ) {
 
 			return;
 
 		}
 
-		//
-
-		// pick a point 1 unit out along the ray to avoid the ray origin
-		// sitting at the camera origin which will cause "w" to be 0 when
-		// applying the projection matrix.
-		ray.at( 1, _ssOrigin );
-
-		// ndc space [ - 1.0, 1.0 ]
-		_ssOrigin.w = 1;
-		_ssOrigin.applyMatrix4( camera.matrixWorldInverse );
-		_ssOrigin.applyMatrix4( projectionMatrix );
-		_ssOrigin.multiplyScalar( 1 / _ssOrigin.w );
-
-		// screen space
-		_ssOrigin.x *= resolution.x / 2;
-		_ssOrigin.y *= resolution.y / 2;
-		_ssOrigin.z = 0;
-
-		_ssOrigin3.copy( _ssOrigin );
-
-		_mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
-
-		for ( let i = 0, l = instanceStart.count; i < l; i ++ ) {
-
-			_start4.fromBufferAttribute( instanceStart, i );
-			_end4.fromBufferAttribute( instanceEnd, i );
-
-			_start4.w = 1;
-			_end4.w = 1;
-
-			// camera space
-			_start4.applyMatrix4( _mvMatrix );
-			_end4.applyMatrix4( _mvMatrix );
-
-			// skip the segment if it's entirely behind the camera
-			const isBehindCameraNear = _start4.z > near && _end4.z > near;
-			if ( isBehindCameraNear ) {
-
-				continue;
-
-			}
-
-			// trim the segment if it extends behind camera near
-			if ( _start4.z > near ) {
-
-				const deltaDist = _start4.z - _end4.z;
-				const t = ( _start4.z - near ) / deltaDist;
-				_start4.lerp( _end4, t );
-
-			} else if ( _end4.z > near ) {
-
-				const deltaDist = _end4.z - _start4.z;
-				const t = ( _end4.z - near ) / deltaDist;
-				_end4.lerp( _start4, t );
-
-			}
-
-			// clip space
-			_start4.applyMatrix4( projectionMatrix );
-			_end4.applyMatrix4( projectionMatrix );
-
-			// ndc space [ - 1.0, 1.0 ]
-			_start4.multiplyScalar( 1 / _start4.w );
-			_end4.multiplyScalar( 1 / _end4.w );
-
-			// screen space
-			_start4.x *= resolution.x / 2;
-			_start4.y *= resolution.y / 2;
+		// check if we intersect the box bounds
+		if ( geometry.boundingBox === null ) {
 
-			_end4.x *= resolution.x / 2;
-			_end4.y *= resolution.y / 2;
+			geometry.computeBoundingBox();
 
-			// create 2d segment
-			_line.start.copy( _start4 );
-			_line.start.z = 0;
+		}
 
-			_line.end.copy( _end4 );
-			_line.end.z = 0;
+		_box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld );
 
-			// get closest point on ray to segment
-			const param = _line.closestPointToPointParameter( _ssOrigin3, true );
-			_line.at( param, _closestPoint );
+		// increase the box bounds by the worst case line width
+		let boxMargin;
+		if ( worldUnits ) {
 
-			// check if the intersection point is within clip space
-			const zPos = MathUtils.lerp( _start4.z, _end4.z, param );
-			const isInClipSpace = zPos >= - 1 && zPos <= 1;
+			boxMargin = _lineWidth * 0.5;
 
-			const isInside = _ssOrigin3.distanceTo( _closestPoint ) < lineWidth * 0.5;
+		} else {
 
-			if ( isInClipSpace && isInside ) {
+			const distanceToBox = Math.max( camera.near, _box.distanceToPoint( _ray.origin ) );
+			boxMargin = getWorldSpaceHalfWidth( camera, distanceToBox, material.resolution );
 
-				_line.start.fromBufferAttribute( instanceStart, i );
-				_line.end.fromBufferAttribute( instanceEnd, i );
+		}
 
-				_line.start.applyMatrix4( matrixWorld );
-				_line.end.applyMatrix4( matrixWorld );
+		_box.expandByScalar( boxMargin );
 
-				const pointOnLine = new Vector3();
-				const point = new Vector3();
+		if ( _ray.intersectsBox( _box ) === false ) {
 
-				ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine );
+			return;
 
-				intersects.push( {
+		}
 
-					point: point,
-					pointOnLine: pointOnLine,
-					distance: ray.origin.distanceTo( point ),
+		if ( worldUnits ) {
 
-					object: this,
-					face: null,
-					faceIndex: i,
-					uv: null,
-					uv2: null,
+			raycastWorldUnits( this, intersects );
 
-				} );
+		} else {
 
-			}
+			raycastScreenSpace( this, camera, intersects );
 
 		}
 

+ 1 - 1
examples/jsm/lines/Wireframe.js

@@ -20,7 +20,7 @@ class Wireframe extends Mesh {
 
 	}
 
-	// for backwards-compatability, but could be a method of LineSegmentsGeometry...
+	// for backwards-compatibility, but could be a method of LineSegmentsGeometry...
 
 	computeLineDistances() {
 

BIN
examples/screenshots/webgl_lines_fat_raycasting.jpg


+ 1 - 0
examples/tags.json

@@ -29,6 +29,7 @@
 	"webgl_lights_hemisphere": [ "directional" ],
 	"webgl_lights_pointlights": [ "multiple" ],
 	"webgl_lines_fat": [ "gpu", "stats", "panel" ],
+	"webgl_lines_fat_raycasting": [ "gpu", "stats", "panel", "raycast" ],
 	"webgl_loader_ttf": [ "text", "font" ],
 	"webgl_loader_pdb": [ "molecules", "css2d" ],
 	"webgl_loader_ldraw": [ "lego" ],

+ 380 - 0
examples/webgl_lines_fat_raycasting.html

@@ -0,0 +1,380 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - lines - fat</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>
+
+		<div id="container"></div>
+
+		<div id="info"><a href="https://threejs.org" target="_blank">three.js</a> - fat lines raycasting</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"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import Stats from './jsm/libs/stats.module.js';
+			import { GPUStatsPanel } from './jsm/utils/GPUStatsPanel.js';
+
+			import { GUI } from './jsm/libs/lil-gui.module.min.js';
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { LineMaterial } from './jsm/lines/LineMaterial.js';
+			import { LineSegments2 } from './jsm/lines/LineSegments2.js';
+			import { LineSegmentsGeometry } from './jsm/lines/LineSegmentsGeometry.js';
+			import { Line2 } from './jsm/lines/Line2.js';
+			import { LineGeometry } from './jsm/lines/LineGeometry.js';
+
+			let line, thresholdLine, segments, thresholdSegments;
+			let renderer, scene, camera, camera2, controls;
+			let raycaster, sphereInter, sphereOnLine;
+			let matLine, matThresholdLine;
+			let stats, gpuPanel;
+			let gui;
+
+			// viewport
+			let insetWidth;
+			let insetHeight;
+
+			const pointer = new THREE.Vector2( Infinity, Infinity );
+
+			init();
+			animate();
+
+			function init() {
+
+				renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setClearColor( 0x000000, 0.0 );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( - 40, 0, 60 );
+
+				camera2 = new THREE.PerspectiveCamera( 40, 1, 1, 1000 );
+				camera2.position.copy( camera.position );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 10;
+				controls.maxDistance = 500;
+
+				raycaster = new THREE.Raycaster();
+				raycaster.params.Line2 = {};
+				raycaster.params.Line2.threshold = 0;
+
+				const sphereGeometry = new THREE.SphereGeometry( 0.25 );
+				const sphereInterMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, depthTest: false } );
+				const sphereOnLineMaterial = new THREE.MeshBasicMaterial( { color: 0x00ff00, depthTest: false } );
+
+				sphereInter = new THREE.Mesh( sphereGeometry, sphereInterMaterial );
+				sphereOnLine = new THREE.Mesh( sphereGeometry, sphereOnLineMaterial );
+				sphereInter.visible = false;
+				sphereOnLine.visible = false;
+				sphereInter.renderOrder = 10;
+				sphereOnLine.renderOrder = 10;
+				scene.add( sphereInter );
+				scene.add( sphereOnLine );
+
+				// Position and THREE.Color Data
+
+				const positions = [];
+				const colors = [];
+				const points = [];
+				for ( let i = - 50; i < 50; i ++ ) {
+
+					const t = i / 3;
+					points.push( new THREE.Vector3( t * Math.sin( 2 * t ), t, t * Math.cos( 2 * t ) ) );
+
+				}
+
+				const spline = new THREE.CatmullRomCurve3( points );
+				const divisions = Math.round( 3 * points.length );
+				const point = new THREE.Vector3();
+				const color = new THREE.Color();
+
+				for ( let i = 0, l = divisions; i < l; i ++ ) {
+
+					const t = i / l;
+
+					spline.getPoint( t, point );
+					positions.push( point.x, point.y, point.z );
+
+					color.setHSL( t, 1.0, 0.5 );
+					colors.push( color.r, color.g, color.b );
+
+				}
+
+				const lineGeometry = new LineGeometry();
+				lineGeometry.setPositions( positions );
+				lineGeometry.setColors( colors );
+
+				const segmentsGeometry = new LineSegmentsGeometry();
+				segmentsGeometry.setPositions( positions );
+				segmentsGeometry.setColors( colors );
+
+				matLine = new LineMaterial( {
+
+					color: 0xffffff,
+					linewidth: 1, // in world units with size attenuation, pixels otherwise
+					worldUnits: true,
+					vertexColors: true,
+
+					//resolution:  // to be set by renderer, eventually
+					alphaToCoverage: true,
+
+				} );
+
+				matThresholdLine = new LineMaterial( {
+
+					color: 0xffffff,
+					linewidth: matLine.linewidth, // in world units with size attenuation, pixels otherwise
+					worldUnits: true,
+					// vertexColors: true,
+					transparent: true,
+					opacity: 0.2,
+					depthTest: false,
+					visible: false,
+					//resolution:  // to be set by renderer, eventually
+
+				} );
+
+				segments = new LineSegments2( segmentsGeometry, matLine );
+				segments.computeLineDistances();
+				segments.scale.set( 1, 1, 1 );
+				scene.add( segments );
+				segments.visible = false;
+
+				thresholdSegments = new LineSegments2( segmentsGeometry, matThresholdLine );
+				thresholdSegments.computeLineDistances();
+				thresholdSegments.scale.set( 1, 1, 1 );
+				scene.add( thresholdSegments );
+				thresholdSegments.visible = false;
+
+				line = new Line2( lineGeometry, matLine );
+				line.computeLineDistances();
+				line.scale.set( 1, 1, 1 );
+				scene.add( line );
+
+				thresholdLine = new Line2( lineGeometry, matThresholdLine );
+				thresholdLine.computeLineDistances();
+				thresholdLine.scale.set( 1, 1, 1 );
+				scene.add( thresholdLine );
+
+				const geo = new THREE.BufferGeometry();
+				geo.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
+				geo.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
+
+				//
+				document.addEventListener( 'pointermove', onPointerMove );
+				window.addEventListener( 'resize', onWindowResize );
+				onWindowResize();
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				gpuPanel = new GPUStatsPanel( renderer.getContext() );
+				stats.addPanel( gpuPanel );
+				stats.showPanel( 0 );
+				initGui();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				insetWidth = window.innerHeight / 4; // square
+				insetHeight = window.innerHeight / 4;
+
+				camera2.aspect = insetWidth / insetHeight;
+				camera2.updateProjectionMatrix();
+
+			}
+
+			function onPointerMove( event ) {
+
+				pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1;
+				pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				stats.update();
+
+				// main scene
+
+				renderer.setClearColor( 0x000000, 0 );
+
+				renderer.setViewport( 0, 0, window.innerWidth, window.innerHeight );
+
+				raycaster.setFromCamera( pointer, camera );
+
+				const obj = line.visible ? line : segments;
+				const intersects = raycaster.intersectObject( obj, true );
+
+				if ( intersects.length > 0 ) {
+
+					sphereInter.visible = true;
+					sphereOnLine.visible = true;
+					sphereInter.position.copy( intersects[ 0 ].point );
+					sphereOnLine.position.copy( intersects[ 0 ].pointOnLine );
+					const i = intersects[ 0 ].faceIndex;
+					const colors = obj.geometry.getAttribute( 'instanceColorStart' );
+					const color = new THREE.Color().setRGB( colors.getX( i ), colors.getY( i ), colors.getZ( i ) );
+					sphereInter.material.color.copy( color.clone().offsetHSL( 0.3, 0, 0 ) );
+					sphereOnLine.material.color.copy( color.clone().offsetHSL( 0.7, 0, 0 ) );
+					renderer.domElement.style.cursor = 'crosshair';
+
+				} else {
+
+					sphereInter.visible = false;
+					sphereOnLine.visible = false;
+					renderer.domElement.style.cursor = '';
+
+				}
+
+				// renderer will set this eventually
+				matLine.resolution.set( window.innerWidth, window.innerHeight ); // resolution of the viewport
+				matThresholdLine.resolution.set( window.innerWidth, window.innerHeight ); // resolution of the viewport
+
+				gpuPanel.startQuery();
+				renderer.render( scene, camera );
+				gpuPanel.endQuery();
+
+				// inset scene
+
+				renderer.setClearColor( 0x222222, 1 );
+
+				renderer.clearDepth(); // important!
+
+				renderer.setScissorTest( true );
+
+				renderer.setScissor( 20, 20, insetWidth, insetHeight );
+
+				renderer.setViewport( 20, 20, insetWidth, insetHeight );
+
+				camera2.position.copy( camera.position );
+				camera2.quaternion.copy( camera.quaternion );
+
+				// renderer will set this eventually
+				matLine.resolution.set( insetWidth, insetHeight ); // resolution of the inset viewport
+
+				renderer.render( scene, camera2 );
+
+				renderer.setScissorTest( false );
+
+			}
+
+			//
+
+			function switchLine( val ) {
+
+				switch ( val ) {
+
+					case 0:
+						line.visible = true;
+						thresholdLine.visible = true;
+
+						segments.visible = false;
+						thresholdSegments.visible = false;
+
+						break;
+
+					case 1:
+						line.visible = false;
+						thresholdLine.visible = false;
+
+						segments.visible = true;
+						thresholdSegments.visible = true;
+
+						break;
+
+				}
+
+			}
+
+			function initGui() {
+
+				gui = new GUI();
+
+				const param = {
+					'line type': 0,
+					'world units': matLine.worldUnits,
+					'visualize threshold': matThresholdLine.visible,
+					'width': matLine.linewidth,
+					'alphaToCoverage': matLine.alphaToCoverage,
+					'threshold': raycaster.params.Line2.threshold
+				};
+
+				gui.add( param, 'line type', { 'LineGeometry': 0, 'LineSegmentsGeometry': 1 } ).onChange( function ( val ) {
+
+					switchLine( val );
+
+				} ).setValue( 1 );
+
+				gui.add( param, 'world units' ).onChange( function ( val ) {
+
+					matLine.worldUnits = val;
+					matLine.needsUpdate = true;
+					matThresholdLine.worldUnits = val;
+					matThresholdLine.needsUpdate = true;
+
+				} );
+
+				gui.add( param, 'visualize threshold' ).onChange( function ( val ) {
+
+					matThresholdLine.visible = val;
+
+				} );
+
+				gui.add( param, 'width', 1, 10 ).onChange( function ( val ) {
+
+					matLine.linewidth = val;
+					matThresholdLine.linewidth = matLine.linewidth + raycaster.params.Line2.threshold;
+
+				} );
+
+				gui.add( param, 'alphaToCoverage' ).onChange( function ( val ) {
+
+					matLine.alphaToCoverage = val;
+
+				} );
+
+				gui.add( param, 'threshold', 0, 10 ).onChange( function ( val ) {
+
+					raycaster.params.Line2.threshold = val;
+					matThresholdLine.linewidth = matLine.linewidth + raycaster.params.Line2.threshold;
+
+				} );
+
+			}
+
+		</script>
+
+	</body>
+
+</html>

+ 1 - 1
src/materials/Material.js

@@ -122,7 +122,7 @@ class Material extends EventDispatcher {
 
 			}
 
-			// for backward compatability if shading is set in the constructor
+			// for backward compatibility if shading is set in the constructor
 			if ( key === 'shading' ) {
 
 				console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );