-
Notifications
You must be signed in to change notification settings - Fork 11
/
LineSegments2.js
315 lines (192 loc) · 8.1 KB
/
LineSegments2.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
import {
Box3,
BufferGeometry,
InstancedInterleavedBuffer,
InterleavedBufferAttribute,
Line3,
MathUtils,
Matrix4,
Mesh,
Sphere,
Vector3,
Vector4
} from 'three';
const THREE = window.THREE
? window.THREE // Prefer consumption from global THREE, if exists
: {
Box3,
BufferGeometry,
InstancedInterleavedBuffer,
InterleavedBufferAttribute,
Line3,
MathUtils,
Matrix4,
Mesh,
Sphere,
Vector3,
Vector4
};
import LineSegmentsGeometry from "./LineSegmentsGeometry.js";
import LineMaterial from "./LineMaterial.js";
// support both modes for backwards threejs compatibility
var setAttributeFn = new THREE.BufferGeometry().setAttribute ? 'setAttribute' : 'addAttribute';
const _start = new THREE.Vector3();
const _end = new THREE.Vector3();
const _start4 = new THREE.Vector4();
const _end4 = new THREE.Vector4();
const _ssOrigin = new THREE.Vector4();
const _ssOrigin3 = new THREE.Vector3();
const _mvMatrix = new THREE.Matrix4();
const _line = new THREE.Line3();
const _closestPoint = new THREE.Vector3();
const _box = new THREE.Box3();
const _sphere = new THREE.Sphere();
const _clipToWorldVector = new THREE.Vector4();
class LineSegments2 extends THREE.Mesh {
constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( {
color: Math.random() * 0xffffff
} ) ) {
super( geometry, material );
this.type = 'LineSegments2';
} // for backwards-compatability, but could be a method of LineSegmentsGeometry...
computeLineDistances() {
const geometry = this.geometry;
const instanceStart = geometry.attributes.instanceStart;
const instanceEnd = geometry.attributes.instanceEnd;
const lineDistances = new Float32Array( 2 * instanceStart.count );
for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) {
_start.fromBufferAttribute( instanceStart, i );
_end.fromBufferAttribute( instanceEnd, i );
lineDistances[ j ] = j === 0 ? 0 : lineDistances[ j - 1 ];
lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end );
}
const instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
geometry[setAttributeFn]( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
geometry[setAttributeFn]( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
return this;
}
raycast( raycaster, intersects ) {
if ( raycaster.camera === null ) {
console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );
}
const threshold = raycaster.params.Line2 !== undefined ? raycaster.params.Line2.threshold || 0 : 0;
const ray = raycaster.ray;
const camera = raycaster.camera;
const projectionMatrix = camera.projectionMatrix;
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; // camera forward is negative
const near = - camera.near; // clip space is [ - 1, 1 ] so multiply by two to get the full
// width in clip space
const ssMaxWidth = 2.0 * Math.max( lineWidth / resolution.width, lineWidth / resolution.height ); //
// check if we intersect the sphere bounds
if ( geometry.boundingSphere === null ) {
geometry.computeBoundingSphere();
}
_sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld );
const distanceToSphere = Math.max( camera.near, _sphere.distanceToPoint( ray.origin ) ); // get the w component to scale the world space line width
_clipToWorldVector.set( 0, 0, - distanceToSphere, 1.0 ).applyMatrix4( camera.projectionMatrix );
_clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w );
_clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse ); // increase the sphere bounds by the worst case line screen space width
const sphereMargin = Math.abs( ssMaxWidth / _clipToWorldVector.w ) * 0.5;
_sphere.radius += sphereMargin;
if ( raycaster.ray.intersectsSphere( _sphere ) === false ) {
return;
} //
// check if we intersect the box bounds
if ( geometry.boundingBox === null ) {
geometry.computeBoundingBox();
}
_box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld );
const distanceToBox = Math.max( camera.near, _box.distanceToPoint( ray.origin ) ); // get the w component to scale the world space line width
_clipToWorldVector.set( 0, 0, - distanceToBox, 1.0 ).applyMatrix4( camera.projectionMatrix );
_clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w );
_clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse ); // increase the sphere bounds by the worst case line screen space width
const boxMargin = Math.abs( ssMaxWidth / _clipToWorldVector.w ) * 0.5;
_box.max.x += boxMargin;
_box.max.y += boxMargin;
_box.max.z += boxMargin;
_box.min.x -= boxMargin;
_box.min.y -= boxMargin;
_box.min.z -= boxMargin;
if ( raycaster.ray.intersectsBox( _box ) === 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 );
_start.w = 1;
_end.w = 1; // camera space
_start4.applyMatrix4( _mvMatrix );
_end4.applyMatrix4( _mvMatrix ); // skip the segment if it's entirely behind the camera
var 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 = THREE.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 THREE.Vector3();
const 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
} );
}
}
}
}
LineSegments2.prototype.LineSegments2 = true;
export default LineSegments2;