Skip to content
138 changes: 137 additions & 1 deletion examples/js/lines/LineSegments2.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,142 @@ THREE.LineSegments2.prototype = Object.assign( Object.create( THREE.Mesh.prototy

};

}() )
}() ),

raycast: ( function () {

var start = new THREE.Vector4();
var end = new THREE.Vector4();

var ssOrigin = new THREE.Vector4();
var ssOrigin3 = new THREE.Vector3();
var mvMatrix = new THREE.Matrix4();
var line = new THREE.Line3();
var closestPoint = new THREE.Vector3();

return function raycast( raycaster, intersects ) {

if ( raycaster.camera === null ) {

console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );

}

var ray = raycaster.ray;
var camera = raycaster.camera;
var projectionMatrix = camera.projectionMatrix;

var geometry = this.geometry;
var material = this.material;
var resolution = material.resolution;
var lineWidth = material.linewidth;

var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;

// ndc space [ - 1.0, 1.0 ]
ray.at( 1, ssOrigin );
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 );

var matrixWorld = this.matrixWorld;
mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );

for ( var i = 0, l = instanceStart.count; i < l; i ++ ) {

start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );

start.w = 1;
end.w = 1;

// camera space
start.applyMatrix4( mvMatrix );
end.applyMatrix4( mvMatrix );

// clip space
start.applyMatrix4( projectionMatrix );
end.applyMatrix4( projectionMatrix );

// ndc space [ - 1.0, 1.0 ]
start.multiplyScalar( 1 / start.w );
end.multiplyScalar( 1 / end.w );

// skip the segment if it's outside the camera near and far planes
var isBehindCameraNear = start.z < - 1 && end.z < - 1;
var isPastCameraFar = start.z > 1 && end.z > 1;
if ( isBehindCameraNear || isPastCameraFar ) {

continue;

}

// screen space
start.x *= resolution.x / 2;
start.y *= resolution.y / 2;

end.x *= resolution.x / 2;
end.y *= resolution.y / 2;

// create 2d segment
line.start.copy( start );
line.start.z = 0;

line.end.copy( end );
line.end.z = 0;

// get closest point on ray to segment
var param = line.closestPointToPointParameter( ssOrigin3, true );
line.at( param, closestPoint );

// check if the intersection point is within clip space
var zPos = THREE.Math.lerp( start.z, end.z, param );
var isInClipSpace = zPos >= -1 && zPos <= 1;

var 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 );

var pointOnLine = new THREE.Vector3();
var point = new THREE.Vector3();

ray.distanceSqToSegment( line.start, line.end, point, pointOnLine );

intersects.push( {

point: point,
pointOnLine: pointOnLine,
distance: ray.origin.distanceTo( point ),

object: this,
face: null,
faceIndex: i,
uv: null,
uv2: null,

} );

}

}

}

} () )

} );
144 changes: 142 additions & 2 deletions examples/jsm/lines/LineSegments2.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
import {
InstancedInterleavedBuffer,
InterleavedBufferAttribute,
Line3,
Math as _Math,
Matrix4,
Mesh,
Vector3
Vector3,
Vector4
} from "../../../build/three.module.js";
import { LineSegmentsGeometry } from "../lines/LineSegmentsGeometry.js";
import { LineMaterial } from "../lines/LineMaterial.js";
Expand Down Expand Up @@ -61,7 +65,143 @@ LineSegments2.prototype = Object.assign( Object.create( Mesh.prototype ), {

};

}() )
}() ),

raycast: ( function () {

var start = new Vector4();
var end = new Vector4();

var ssOrigin = new Vector4();
var ssOrigin3 = new Vector3();
var mvMatrix = new Matrix4();
var line = new Line3();
var closestPoint = new Vector3();

return function raycast( raycaster, intersects ) {

if ( raycaster.camera === null ) {

console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );

}

var ray = raycaster.ray;
var camera = raycaster.camera;
var projectionMatrix = camera.projectionMatrix;

var geometry = this.geometry;
var material = this.material;
var resolution = material.resolution;
var lineWidth = material.linewidth;

var instanceStart = geometry.attributes.instanceStart;
var instanceEnd = geometry.attributes.instanceEnd;

// ndc space [ - 1.0, 1.0 ]
ray.at( 1, ssOrigin );
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 );

var matrixWorld = this.matrixWorld;
mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );

for ( var i = 0, l = instanceStart.count; i < l; i ++ ) {

start.fromBufferAttribute( instanceStart, i );
end.fromBufferAttribute( instanceEnd, i );

start.w = 1;
end.w = 1;

// camera space
start.applyMatrix4( mvMatrix );
end.applyMatrix4( mvMatrix );

// clip space
start.applyMatrix4( projectionMatrix );
end.applyMatrix4( projectionMatrix );

// ndc space [ - 1.0, 1.0 ]
start.multiplyScalar( 1 / start.w );
end.multiplyScalar( 1 / end.w );

// skip the segment if it's outside the camera near and far planes
var isBehindCameraNear = start.z < - 1 && end.z < - 1;
var isPastCameraFar = start.z > 1 && end.z > 1;
if ( isBehindCameraNear || isPastCameraFar ) {

continue;

}

// screen space
start.x *= resolution.x / 2;
start.y *= resolution.y / 2;

end.x *= resolution.x / 2;
end.y *= resolution.y / 2;

// create 2d segment
line.start.copy( start );
line.start.z = 0;

line.end.copy( end );
line.end.z = 0;

// get closest point on ray to segment
var param = line.closestPointToPointParameter( ssOrigin3, true );
line.at( param, closestPoint );

// check if the intersection point is within clip space
var zPos = _Math.lerp( start.z, end.z, param );
var isInClipSpace = zPos >= -1 && zPos <= 1;

var 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 );

var pointOnLine = new Vector3();
var point = new Vector3();

ray.distanceSqToSegment( line.start, line.end, point, pointOnLine );

intersects.push( {

point: point,
pointOnLine: pointOnLine,
distance: ray.origin.distanceTo( point ),

object: this,
face: null,
faceIndex: i,
uv: null,
uv2: null,

} );

}

}

}

} () )

} );

Expand Down
40 changes: 40 additions & 0 deletions examples/webgl_lines_fat.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
var matLine, matLineBasic, matLineDashed;
var stats;
var gui;
var mouse, raycaster, raycastPoint;

// viewport
var insetWidth;
Expand Down Expand Up @@ -123,6 +124,14 @@
window.addEventListener( 'resize', onWindowResize, false );
onWindowResize();

mouse = new THREE.Vector2();
raycaster = new THREE.Raycaster();
raycastPoint = new THREE.Mesh( new THREE.SphereBufferGeometry( 1, 20, 10 ) );
raycastPoint.material.color.set( 0xff0000 );
raycastPoint.scale.setScalar( 0.25 );
scene.add( raycastPoint );
document.addEventListener( 'mousemove', onMouseMove, false );

stats = new Stats();
document.body.appendChild( stats.dom );

Expand All @@ -145,6 +154,33 @@

}

function onMouseMove( e ) {

e.preventDefault();

mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;

}

function doRaycast() {

camera.updateMatrixWorld();
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( [ line, line1 ] );
if ( intersects.length !== 0 ) {

raycastPoint.position.copy( intersects[ 0 ].point );
raycastPoint.visible = true;

} else {

raycastPoint.visible = false;

}

}

function animate() {

requestAnimationFrame( animate );
Expand All @@ -160,6 +196,10 @@
// renderer will set this eventually
matLine.resolution.set( window.innerWidth, window.innerHeight ); // resolution of the viewport

// perform raycast after the resolution has been updated for the camera
// view we care about
doRaycast();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be a similar case to what was discussed in #16423 where sprites needed more contextual about the camera being used in order to properly be raycast against. In this case the renderer canvas that is being clicked should dictate the resolution that is used when raycasting against the lines which means the renderer or the resolution should be available as context for the cast. Otherwise we have this scenario where the last call made to renderer.render will change the outcome of the raycast which would be confusing.

One solution is to add a raycaster.resolution or raycaster.renderer field which could be required when raycasting against these lines much like raycaster.camera but I'd be happy to hear other thoughts!


renderer.render( scene, camera );

// inset scene
Expand Down