-
-
Notifications
You must be signed in to change notification settings - Fork 35.7k
/
Copy pathLine.js
328 lines (223 loc) · 8.12 KB
/
Line.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
316
317
318
319
320
321
322
323
324
325
326
327
328
import { Sphere } from '../math/Sphere.js';
import { Ray } from '../math/Ray.js';
import { Matrix4 } from '../math/Matrix4.js';
import { Object3D } from '../core/Object3D.js';
import { Vector3 } from '../math/Vector3.js';
import { LineBasicMaterial } from '../materials/LineBasicMaterial.js';
import { BufferGeometry } from '../core/BufferGeometry.js';
import { Float32BufferAttribute } from '../core/BufferAttribute.js';
const _vStart = /*@__PURE__*/ new Vector3();
const _vEnd = /*@__PURE__*/ new Vector3();
const _inverseMatrix = /*@__PURE__*/ new Matrix4();
const _ray = /*@__PURE__*/ new Ray();
const _sphere = /*@__PURE__*/ new Sphere();
const _intersectPointOnRay = /*@__PURE__*/ new Vector3();
const _intersectPointOnSegment = /*@__PURE__*/ new Vector3();
/**
* A continuous line. The line are rendered by connecting consecutive
* vertices with straight lines.
*
* ```js
* const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
*
* const points = [];
* points.push( new THREE.Vector3( - 10, 0, 0 ) );
* points.push( new THREE.Vector3( 0, 10, 0 ) );
* points.push( new THREE.Vector3( 10, 0, 0 ) );
*
* const geometry = new THREE.BufferGeometry().setFromPoints( points );
*
* const line = new THREE.Line( geometry, material );
* scene.add( line );
* ```
*
* @augments Object3D
*/
class Line extends Object3D {
/**
* Constructs a new line.
*
* @param {BufferGeometry} [geometry] - The line geometry.
* @param {Material|Array<Material>} [material] - The line material.
*/
constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) {
super();
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isLine = true;
this.type = 'Line';
/**
* The line geometry.
*
* @type {BufferGeometry}
*/
this.geometry = geometry;
/**
* The line material.
*
* @type {Material|Array<Material>}
* @default LineBasicMaterial
*/
this.material = material;
/**
* A dictionary representing the morph targets in the geometry. The key is the
* morph targets name, the value its attribute index. This member is `undefined`
* by default and only set when morph targets are detected in the geometry.
*
* @type {Object<String,number>|undefined}
* @default undefined
*/
this.morphTargetDictionary = undefined;
/**
* An array of weights typically in the range `[0,1]` that specify how much of the morph
* is applied. This member is `undefined` by default and only set when morph targets are
* detected in the geometry.
*
* @type {Array<number>|undefined}
* @default undefined
*/
this.morphTargetInfluences = undefined;
this.updateMorphTargets();
}
copy( source, recursive ) {
super.copy( source, recursive );
this.material = Array.isArray( source.material ) ? source.material.slice() : source.material;
this.geometry = source.geometry;
return this;
}
/**
* Computes an array of distance values which are necessary for rendering dashed lines.
* For each vertex in the geometry, the method calculates the cumulative length from the
* current point to the very beginning of the line.
*
* @return {Line} A reference to this line.
*/
computeLineDistances() {
const geometry = this.geometry;
// we assume non-indexed geometry
if ( geometry.index === null ) {
const positionAttribute = geometry.attributes.position;
const lineDistances = [ 0 ];
for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) {
_vStart.fromBufferAttribute( positionAttribute, i - 1 );
_vEnd.fromBufferAttribute( positionAttribute, i );
lineDistances[ i ] = lineDistances[ i - 1 ];
lineDistances[ i ] += _vStart.distanceTo( _vEnd );
}
geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
} else {
console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
}
return this;
}
/**
* Computes intersection points between a casted ray and this line.
*
* @param {Raycaster} raycaster - The raycaster.
* @param {Array<Object>} intersects - The target array that holds the intersection points.
*/
raycast( raycaster, intersects ) {
const geometry = this.geometry;
const matrixWorld = this.matrixWorld;
const threshold = raycaster.params.Line.threshold;
const drawRange = geometry.drawRange;
// Checking boundingSphere distance to ray
if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
_sphere.copy( geometry.boundingSphere );
_sphere.applyMatrix4( matrixWorld );
_sphere.radius += threshold;
if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
//
_inverseMatrix.copy( matrixWorld ).invert();
_ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
const localThresholdSq = localThreshold * localThreshold;
const step = this.isLineSegments ? 2 : 1;
const index = geometry.index;
const attributes = geometry.attributes;
const positionAttribute = attributes.position;
if ( index !== null ) {
const start = Math.max( 0, drawRange.start );
const end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
for ( let i = start, l = end - 1; i < l; i += step ) {
const a = index.getX( i );
const b = index.getX( i + 1 );
const intersect = checkIntersection( this, raycaster, _ray, localThresholdSq, a, b, i );
if ( intersect ) {
intersects.push( intersect );
}
}
if ( this.isLineLoop ) {
const a = index.getX( end - 1 );
const b = index.getX( start );
const intersect = checkIntersection( this, raycaster, _ray, localThresholdSq, a, b, end - 1 );
if ( intersect ) {
intersects.push( intersect );
}
}
} else {
const start = Math.max( 0, drawRange.start );
const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
for ( let i = start, l = end - 1; i < l; i += step ) {
const intersect = checkIntersection( this, raycaster, _ray, localThresholdSq, i, i + 1, i );
if ( intersect ) {
intersects.push( intersect );
}
}
if ( this.isLineLoop ) {
const intersect = checkIntersection( this, raycaster, _ray, localThresholdSq, end - 1, start, end - 1 );
if ( intersect ) {
intersects.push( intersect );
}
}
}
}
/**
* Sets the values of {@link Line#morphTargetDictionary} and {@link Line#morphTargetInfluences}
* to make sure existing morph targets can influence this 3D object.
*/
updateMorphTargets() {
const geometry = this.geometry;
const morphAttributes = geometry.morphAttributes;
const keys = Object.keys( morphAttributes );
if ( keys.length > 0 ) {
const morphAttribute = morphAttributes[ keys[ 0 ] ];
if ( morphAttribute !== undefined ) {
this.morphTargetInfluences = [];
this.morphTargetDictionary = {};
for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
const name = morphAttribute[ m ].name || String( m );
this.morphTargetInfluences.push( 0 );
this.morphTargetDictionary[ name ] = m;
}
}
}
}
}
function checkIntersection( object, raycaster, ray, thresholdSq, a, b, i ) {
const positionAttribute = object.geometry.attributes.position;
_vStart.fromBufferAttribute( positionAttribute, a );
_vEnd.fromBufferAttribute( positionAttribute, b );
const distSq = ray.distanceSqToSegment( _vStart, _vEnd, _intersectPointOnRay, _intersectPointOnSegment );
if ( distSq > thresholdSq ) return;
_intersectPointOnRay.applyMatrix4( object.matrixWorld ); // Move back to world space for distance calculation
const distance = raycaster.ray.origin.distanceTo( _intersectPointOnRay );
if ( distance < raycaster.near || distance > raycaster.far ) return;
return {
distance: distance,
// What do we want? intersection point on the ray or on the segment??
// point: raycaster.ray.at( distance ),
point: _intersectPointOnSegment.clone().applyMatrix4( object.matrixWorld ),
index: i,
face: null,
faceIndex: null,
barycoord: null,
object: object
};
}
export { Line };