copper3d 3.5.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Scene/copperScene.d.ts +12 -0
- package/dist/Scene/copperScene.js +38 -44
- package/dist/Scene/copperScene.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/MeshGraph.d.ts +40 -0
- package/dist/Utils/surfaceAnnotation/MeshGraph.js +198 -0
- package/dist/Utils/surfaceAnnotation/MeshGraph.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.d.ts +104 -0
- package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.js +403 -0
- package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/annotationStore.d.ts +37 -0
- package/dist/Utils/surfaceAnnotation/annotationStore.js +105 -0
- package/dist/Utils/surfaceAnnotation/annotationStore.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/contourRender.d.ts +7 -0
- package/dist/Utils/surfaceAnnotation/contourRender.js +57 -0
- package/dist/Utils/surfaceAnnotation/contourRender.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/geodesicContour.d.ts +19 -0
- package/dist/Utils/surfaceAnnotation/geodesicContour.js +43 -0
- package/dist/Utils/surfaceAnnotation/geodesicContour.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/index.d.ts +3 -0
- package/dist/Utils/surfaceAnnotation/index.js +2 -0
- package/dist/Utils/surfaceAnnotation/index.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/pointMarkers.d.ts +7 -0
- package/dist/Utils/surfaceAnnotation/pointMarkers.js +16 -0
- package/dist/Utils/surfaceAnnotation/pointMarkers.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/raycastSurface.d.ts +7 -0
- package/dist/Utils/surfaceAnnotation/raycastSurface.js +27 -0
- package/dist/Utils/surfaceAnnotation/raycastSurface.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/strokeContour.d.ts +26 -0
- package/dist/Utils/surfaceAnnotation/strokeContour.js +50 -0
- package/dist/Utils/surfaceAnnotation/strokeContour.js.map +1 -0
- package/dist/Utils/surfaceAnnotation/types.d.ts +40 -0
- package/dist/Utils/surfaceAnnotation/types.js +26 -0
- package/dist/Utils/surfaceAnnotation/types.js.map +1 -0
- package/dist/bundle.esm.js +2883 -79
- package/dist/bundle.umd.js +2883 -78
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/types/Scene/copperScene.d.ts +12 -0
- package/dist/types/Utils/surfaceAnnotation/MeshGraph.d.ts +40 -0
- package/dist/types/Utils/surfaceAnnotation/SurfaceAnnotator.d.ts +104 -0
- package/dist/types/Utils/surfaceAnnotation/annotationStore.d.ts +37 -0
- package/dist/types/Utils/surfaceAnnotation/contourRender.d.ts +7 -0
- package/dist/types/Utils/surfaceAnnotation/geodesicContour.d.ts +19 -0
- package/dist/types/Utils/surfaceAnnotation/index.d.ts +3 -0
- package/dist/types/Utils/surfaceAnnotation/pointMarkers.d.ts +7 -0
- package/dist/types/Utils/surfaceAnnotation/raycastSurface.d.ts +7 -0
- package/dist/types/Utils/surfaceAnnotation/strokeContour.d.ts +26 -0
- package/dist/types/Utils/surfaceAnnotation/types.d.ts +40 -0
- package/dist/types/index.d.ts +5 -3
- package/package.json +1 -1
package/dist/bundle.umd.js
CHANGED
|
@@ -18023,7 +18023,7 @@
|
|
|
18023
18023
|
|
|
18024
18024
|
// Flattening for arrays of vectors and matrices
|
|
18025
18025
|
|
|
18026
|
-
function flatten( array, nBlocks, blockSize ) {
|
|
18026
|
+
function flatten$1( array, nBlocks, blockSize ) {
|
|
18027
18027
|
|
|
18028
18028
|
const firstElem = array[ 0 ];
|
|
18029
18029
|
|
|
@@ -18651,7 +18651,7 @@
|
|
|
18651
18651
|
|
|
18652
18652
|
function setValueV2fArray( gl, v ) {
|
|
18653
18653
|
|
|
18654
|
-
const data = flatten( v, this.size, 2 );
|
|
18654
|
+
const data = flatten$1( v, this.size, 2 );
|
|
18655
18655
|
|
|
18656
18656
|
gl.uniform2fv( this.addr, data );
|
|
18657
18657
|
|
|
@@ -18659,7 +18659,7 @@
|
|
|
18659
18659
|
|
|
18660
18660
|
function setValueV3fArray( gl, v ) {
|
|
18661
18661
|
|
|
18662
|
-
const data = flatten( v, this.size, 3 );
|
|
18662
|
+
const data = flatten$1( v, this.size, 3 );
|
|
18663
18663
|
|
|
18664
18664
|
gl.uniform3fv( this.addr, data );
|
|
18665
18665
|
|
|
@@ -18667,7 +18667,7 @@
|
|
|
18667
18667
|
|
|
18668
18668
|
function setValueV4fArray( gl, v ) {
|
|
18669
18669
|
|
|
18670
|
-
const data = flatten( v, this.size, 4 );
|
|
18670
|
+
const data = flatten$1( v, this.size, 4 );
|
|
18671
18671
|
|
|
18672
18672
|
gl.uniform4fv( this.addr, data );
|
|
18673
18673
|
|
|
@@ -18677,7 +18677,7 @@
|
|
|
18677
18677
|
|
|
18678
18678
|
function setValueM2Array( gl, v ) {
|
|
18679
18679
|
|
|
18680
|
-
const data = flatten( v, this.size, 4 );
|
|
18680
|
+
const data = flatten$1( v, this.size, 4 );
|
|
18681
18681
|
|
|
18682
18682
|
gl.uniformMatrix2fv( this.addr, false, data );
|
|
18683
18683
|
|
|
@@ -18685,7 +18685,7 @@
|
|
|
18685
18685
|
|
|
18686
18686
|
function setValueM3Array( gl, v ) {
|
|
18687
18687
|
|
|
18688
|
-
const data = flatten( v, this.size, 9 );
|
|
18688
|
+
const data = flatten$1( v, this.size, 9 );
|
|
18689
18689
|
|
|
18690
18690
|
gl.uniformMatrix3fv( this.addr, false, data );
|
|
18691
18691
|
|
|
@@ -18693,7 +18693,7 @@
|
|
|
18693
18693
|
|
|
18694
18694
|
function setValueM4Array( gl, v ) {
|
|
18695
18695
|
|
|
18696
|
-
const data = flatten( v, this.size, 16 );
|
|
18696
|
+
const data = flatten$1( v, this.size, 16 );
|
|
18697
18697
|
|
|
18698
18698
|
gl.uniformMatrix4fv( this.addr, false, data );
|
|
18699
18699
|
|
|
@@ -33068,7 +33068,7 @@ void main() {
|
|
|
33068
33068
|
const _vEnd = /*@__PURE__*/ new Vector3();
|
|
33069
33069
|
|
|
33070
33070
|
const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4();
|
|
33071
|
-
const _ray$1 = /*@__PURE__*/ new Ray();
|
|
33071
|
+
const _ray$1$1 = /*@__PURE__*/ new Ray();
|
|
33072
33072
|
const _sphere$1 = /*@__PURE__*/ new Sphere();
|
|
33073
33073
|
|
|
33074
33074
|
const _intersectPointOnRay = /*@__PURE__*/ new Vector3();
|
|
@@ -33155,7 +33155,7 @@ void main() {
|
|
|
33155
33155
|
//
|
|
33156
33156
|
|
|
33157
33157
|
_inverseMatrix$1.copy( matrixWorld ).invert();
|
|
33158
|
-
_ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 );
|
|
33158
|
+
_ray$1$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 );
|
|
33159
33159
|
|
|
33160
33160
|
const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
|
|
33161
33161
|
const localThresholdSq = localThreshold * localThreshold;
|
|
@@ -33176,7 +33176,7 @@ void main() {
|
|
|
33176
33176
|
const a = index.getX( i );
|
|
33177
33177
|
const b = index.getX( i + 1 );
|
|
33178
33178
|
|
|
33179
|
-
const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, a, b );
|
|
33179
|
+
const intersect = checkIntersection( this, raycaster, _ray$1$1, localThresholdSq, a, b );
|
|
33180
33180
|
|
|
33181
33181
|
if ( intersect ) {
|
|
33182
33182
|
|
|
@@ -33191,7 +33191,7 @@ void main() {
|
|
|
33191
33191
|
const a = index.getX( end - 1 );
|
|
33192
33192
|
const b = index.getX( start );
|
|
33193
33193
|
|
|
33194
|
-
const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, a, b );
|
|
33194
|
+
const intersect = checkIntersection( this, raycaster, _ray$1$1, localThresholdSq, a, b );
|
|
33195
33195
|
|
|
33196
33196
|
if ( intersect ) {
|
|
33197
33197
|
|
|
@@ -33208,7 +33208,7 @@ void main() {
|
|
|
33208
33208
|
|
|
33209
33209
|
for ( let i = start, l = end - 1; i < l; i += step ) {
|
|
33210
33210
|
|
|
33211
|
-
const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, i, i + 1 );
|
|
33211
|
+
const intersect = checkIntersection( this, raycaster, _ray$1$1, localThresholdSq, i, i + 1 );
|
|
33212
33212
|
|
|
33213
33213
|
if ( intersect ) {
|
|
33214
33214
|
|
|
@@ -33220,7 +33220,7 @@ void main() {
|
|
|
33220
33220
|
|
|
33221
33221
|
if ( this.isLineLoop ) {
|
|
33222
33222
|
|
|
33223
|
-
const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, end - 1, start );
|
|
33223
|
+
const intersect = checkIntersection( this, raycaster, _ray$1$1, localThresholdSq, end - 1, start );
|
|
33224
33224
|
|
|
33225
33225
|
if ( intersect ) {
|
|
33226
33226
|
|
|
@@ -33300,8 +33300,8 @@ void main() {
|
|
|
33300
33300
|
|
|
33301
33301
|
}
|
|
33302
33302
|
|
|
33303
|
-
const _start = /*@__PURE__*/ new Vector3();
|
|
33304
|
-
const _end = /*@__PURE__*/ new Vector3();
|
|
33303
|
+
const _start$1 = /*@__PURE__*/ new Vector3();
|
|
33304
|
+
const _end$1 = /*@__PURE__*/ new Vector3();
|
|
33305
33305
|
|
|
33306
33306
|
class LineSegments extends Line {
|
|
33307
33307
|
|
|
@@ -33328,11 +33328,11 @@ void main() {
|
|
|
33328
33328
|
|
|
33329
33329
|
for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) {
|
|
33330
33330
|
|
|
33331
|
-
_start.fromBufferAttribute( positionAttribute, i );
|
|
33332
|
-
_end.fromBufferAttribute( positionAttribute, i + 1 );
|
|
33331
|
+
_start$1.fromBufferAttribute( positionAttribute, i );
|
|
33332
|
+
_end$1.fromBufferAttribute( positionAttribute, i + 1 );
|
|
33333
33333
|
|
|
33334
33334
|
lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];
|
|
33335
|
-
lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end );
|
|
33335
|
+
lineDistances[ i + 1 ] = lineDistances[ i ] + _start$1.distanceTo( _end$1 );
|
|
33336
33336
|
|
|
33337
33337
|
}
|
|
33338
33338
|
|
|
@@ -33416,7 +33416,7 @@ void main() {
|
|
|
33416
33416
|
|
|
33417
33417
|
const _inverseMatrix = /*@__PURE__*/ new Matrix4();
|
|
33418
33418
|
const _ray$4 = /*@__PURE__*/ new Ray();
|
|
33419
|
-
const _sphere = /*@__PURE__*/ new Sphere();
|
|
33419
|
+
const _sphere$2 = /*@__PURE__*/ new Sphere();
|
|
33420
33420
|
const _position$2 = /*@__PURE__*/ new Vector3();
|
|
33421
33421
|
|
|
33422
33422
|
class Points extends Object3D {
|
|
@@ -33458,11 +33458,11 @@ void main() {
|
|
|
33458
33458
|
|
|
33459
33459
|
if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
|
|
33460
33460
|
|
|
33461
|
-
_sphere.copy( geometry.boundingSphere );
|
|
33462
|
-
_sphere.applyMatrix4( matrixWorld );
|
|
33463
|
-
_sphere.radius += threshold;
|
|
33461
|
+
_sphere$2.copy( geometry.boundingSphere );
|
|
33462
|
+
_sphere$2.applyMatrix4( matrixWorld );
|
|
33463
|
+
_sphere$2.radius += threshold;
|
|
33464
33464
|
|
|
33465
|
-
if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
|
|
33465
|
+
if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return;
|
|
33466
33466
|
|
|
33467
33467
|
//
|
|
33468
33468
|
|
|
@@ -33762,6 +33762,279 @@ void main() {
|
|
|
33762
33762
|
|
|
33763
33763
|
}
|
|
33764
33764
|
|
|
33765
|
+
class SphereGeometry extends BufferGeometry {
|
|
33766
|
+
|
|
33767
|
+
constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) {
|
|
33768
|
+
|
|
33769
|
+
super();
|
|
33770
|
+
|
|
33771
|
+
this.type = 'SphereGeometry';
|
|
33772
|
+
|
|
33773
|
+
this.parameters = {
|
|
33774
|
+
radius: radius,
|
|
33775
|
+
widthSegments: widthSegments,
|
|
33776
|
+
heightSegments: heightSegments,
|
|
33777
|
+
phiStart: phiStart,
|
|
33778
|
+
phiLength: phiLength,
|
|
33779
|
+
thetaStart: thetaStart,
|
|
33780
|
+
thetaLength: thetaLength
|
|
33781
|
+
};
|
|
33782
|
+
|
|
33783
|
+
widthSegments = Math.max( 3, Math.floor( widthSegments ) );
|
|
33784
|
+
heightSegments = Math.max( 2, Math.floor( heightSegments ) );
|
|
33785
|
+
|
|
33786
|
+
const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );
|
|
33787
|
+
|
|
33788
|
+
let index = 0;
|
|
33789
|
+
const grid = [];
|
|
33790
|
+
|
|
33791
|
+
const vertex = new Vector3();
|
|
33792
|
+
const normal = new Vector3();
|
|
33793
|
+
|
|
33794
|
+
// buffers
|
|
33795
|
+
|
|
33796
|
+
const indices = [];
|
|
33797
|
+
const vertices = [];
|
|
33798
|
+
const normals = [];
|
|
33799
|
+
const uvs = [];
|
|
33800
|
+
|
|
33801
|
+
// generate vertices, normals and uvs
|
|
33802
|
+
|
|
33803
|
+
for ( let iy = 0; iy <= heightSegments; iy ++ ) {
|
|
33804
|
+
|
|
33805
|
+
const verticesRow = [];
|
|
33806
|
+
|
|
33807
|
+
const v = iy / heightSegments;
|
|
33808
|
+
|
|
33809
|
+
// special case for the poles
|
|
33810
|
+
|
|
33811
|
+
let uOffset = 0;
|
|
33812
|
+
|
|
33813
|
+
if ( iy === 0 && thetaStart === 0 ) {
|
|
33814
|
+
|
|
33815
|
+
uOffset = 0.5 / widthSegments;
|
|
33816
|
+
|
|
33817
|
+
} else if ( iy === heightSegments && thetaEnd === Math.PI ) {
|
|
33818
|
+
|
|
33819
|
+
uOffset = - 0.5 / widthSegments;
|
|
33820
|
+
|
|
33821
|
+
}
|
|
33822
|
+
|
|
33823
|
+
for ( let ix = 0; ix <= widthSegments; ix ++ ) {
|
|
33824
|
+
|
|
33825
|
+
const u = ix / widthSegments;
|
|
33826
|
+
|
|
33827
|
+
// vertex
|
|
33828
|
+
|
|
33829
|
+
vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
|
|
33830
|
+
vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
|
|
33831
|
+
vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
|
|
33832
|
+
|
|
33833
|
+
vertices.push( vertex.x, vertex.y, vertex.z );
|
|
33834
|
+
|
|
33835
|
+
// normal
|
|
33836
|
+
|
|
33837
|
+
normal.copy( vertex ).normalize();
|
|
33838
|
+
normals.push( normal.x, normal.y, normal.z );
|
|
33839
|
+
|
|
33840
|
+
// uv
|
|
33841
|
+
|
|
33842
|
+
uvs.push( u + uOffset, 1 - v );
|
|
33843
|
+
|
|
33844
|
+
verticesRow.push( index ++ );
|
|
33845
|
+
|
|
33846
|
+
}
|
|
33847
|
+
|
|
33848
|
+
grid.push( verticesRow );
|
|
33849
|
+
|
|
33850
|
+
}
|
|
33851
|
+
|
|
33852
|
+
// indices
|
|
33853
|
+
|
|
33854
|
+
for ( let iy = 0; iy < heightSegments; iy ++ ) {
|
|
33855
|
+
|
|
33856
|
+
for ( let ix = 0; ix < widthSegments; ix ++ ) {
|
|
33857
|
+
|
|
33858
|
+
const a = grid[ iy ][ ix + 1 ];
|
|
33859
|
+
const b = grid[ iy ][ ix ];
|
|
33860
|
+
const c = grid[ iy + 1 ][ ix ];
|
|
33861
|
+
const d = grid[ iy + 1 ][ ix + 1 ];
|
|
33862
|
+
|
|
33863
|
+
if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );
|
|
33864
|
+
if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );
|
|
33865
|
+
|
|
33866
|
+
}
|
|
33867
|
+
|
|
33868
|
+
}
|
|
33869
|
+
|
|
33870
|
+
// build geometry
|
|
33871
|
+
|
|
33872
|
+
this.setIndex( indices );
|
|
33873
|
+
this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
|
|
33874
|
+
this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
|
|
33875
|
+
this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
|
|
33876
|
+
|
|
33877
|
+
}
|
|
33878
|
+
|
|
33879
|
+
copy( source ) {
|
|
33880
|
+
|
|
33881
|
+
super.copy( source );
|
|
33882
|
+
|
|
33883
|
+
this.parameters = Object.assign( {}, source.parameters );
|
|
33884
|
+
|
|
33885
|
+
return this;
|
|
33886
|
+
|
|
33887
|
+
}
|
|
33888
|
+
|
|
33889
|
+
static fromJSON( data ) {
|
|
33890
|
+
|
|
33891
|
+
return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength );
|
|
33892
|
+
|
|
33893
|
+
}
|
|
33894
|
+
|
|
33895
|
+
}
|
|
33896
|
+
|
|
33897
|
+
class WireframeGeometry extends BufferGeometry {
|
|
33898
|
+
|
|
33899
|
+
constructor( geometry = null ) {
|
|
33900
|
+
|
|
33901
|
+
super();
|
|
33902
|
+
|
|
33903
|
+
this.type = 'WireframeGeometry';
|
|
33904
|
+
|
|
33905
|
+
this.parameters = {
|
|
33906
|
+
geometry: geometry
|
|
33907
|
+
};
|
|
33908
|
+
|
|
33909
|
+
if ( geometry !== null ) {
|
|
33910
|
+
|
|
33911
|
+
// buffer
|
|
33912
|
+
|
|
33913
|
+
const vertices = [];
|
|
33914
|
+
const edges = new Set();
|
|
33915
|
+
|
|
33916
|
+
// helper variables
|
|
33917
|
+
|
|
33918
|
+
const start = new Vector3();
|
|
33919
|
+
const end = new Vector3();
|
|
33920
|
+
|
|
33921
|
+
if ( geometry.index !== null ) {
|
|
33922
|
+
|
|
33923
|
+
// indexed BufferGeometry
|
|
33924
|
+
|
|
33925
|
+
const position = geometry.attributes.position;
|
|
33926
|
+
const indices = geometry.index;
|
|
33927
|
+
let groups = geometry.groups;
|
|
33928
|
+
|
|
33929
|
+
if ( groups.length === 0 ) {
|
|
33930
|
+
|
|
33931
|
+
groups = [ { start: 0, count: indices.count, materialIndex: 0 } ];
|
|
33932
|
+
|
|
33933
|
+
}
|
|
33934
|
+
|
|
33935
|
+
// create a data structure that contains all edges without duplicates
|
|
33936
|
+
|
|
33937
|
+
for ( let o = 0, ol = groups.length; o < ol; ++ o ) {
|
|
33938
|
+
|
|
33939
|
+
const group = groups[ o ];
|
|
33940
|
+
|
|
33941
|
+
const groupStart = group.start;
|
|
33942
|
+
const groupCount = group.count;
|
|
33943
|
+
|
|
33944
|
+
for ( let i = groupStart, l = ( groupStart + groupCount ); i < l; i += 3 ) {
|
|
33945
|
+
|
|
33946
|
+
for ( let j = 0; j < 3; j ++ ) {
|
|
33947
|
+
|
|
33948
|
+
const index1 = indices.getX( i + j );
|
|
33949
|
+
const index2 = indices.getX( i + ( j + 1 ) % 3 );
|
|
33950
|
+
|
|
33951
|
+
start.fromBufferAttribute( position, index1 );
|
|
33952
|
+
end.fromBufferAttribute( position, index2 );
|
|
33953
|
+
|
|
33954
|
+
if ( isUniqueEdge( start, end, edges ) === true ) {
|
|
33955
|
+
|
|
33956
|
+
vertices.push( start.x, start.y, start.z );
|
|
33957
|
+
vertices.push( end.x, end.y, end.z );
|
|
33958
|
+
|
|
33959
|
+
}
|
|
33960
|
+
|
|
33961
|
+
}
|
|
33962
|
+
|
|
33963
|
+
}
|
|
33964
|
+
|
|
33965
|
+
}
|
|
33966
|
+
|
|
33967
|
+
} else {
|
|
33968
|
+
|
|
33969
|
+
// non-indexed BufferGeometry
|
|
33970
|
+
|
|
33971
|
+
const position = geometry.attributes.position;
|
|
33972
|
+
|
|
33973
|
+
for ( let i = 0, l = ( position.count / 3 ); i < l; i ++ ) {
|
|
33974
|
+
|
|
33975
|
+
for ( let j = 0; j < 3; j ++ ) {
|
|
33976
|
+
|
|
33977
|
+
// three edges per triangle, an edge is represented as (index1, index2)
|
|
33978
|
+
// e.g. the first triangle has the following edges: (0,1),(1,2),(2,0)
|
|
33979
|
+
|
|
33980
|
+
const index1 = 3 * i + j;
|
|
33981
|
+
const index2 = 3 * i + ( ( j + 1 ) % 3 );
|
|
33982
|
+
|
|
33983
|
+
start.fromBufferAttribute( position, index1 );
|
|
33984
|
+
end.fromBufferAttribute( position, index2 );
|
|
33985
|
+
|
|
33986
|
+
if ( isUniqueEdge( start, end, edges ) === true ) {
|
|
33987
|
+
|
|
33988
|
+
vertices.push( start.x, start.y, start.z );
|
|
33989
|
+
vertices.push( end.x, end.y, end.z );
|
|
33990
|
+
|
|
33991
|
+
}
|
|
33992
|
+
|
|
33993
|
+
}
|
|
33994
|
+
|
|
33995
|
+
}
|
|
33996
|
+
|
|
33997
|
+
}
|
|
33998
|
+
|
|
33999
|
+
// build geometry
|
|
34000
|
+
|
|
34001
|
+
this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
|
|
34002
|
+
|
|
34003
|
+
}
|
|
34004
|
+
|
|
34005
|
+
}
|
|
34006
|
+
|
|
34007
|
+
copy( source ) {
|
|
34008
|
+
|
|
34009
|
+
super.copy( source );
|
|
34010
|
+
|
|
34011
|
+
this.parameters = Object.assign( {}, source.parameters );
|
|
34012
|
+
|
|
34013
|
+
return this;
|
|
34014
|
+
|
|
34015
|
+
}
|
|
34016
|
+
|
|
34017
|
+
}
|
|
34018
|
+
|
|
34019
|
+
function isUniqueEdge( start, end, edges ) {
|
|
34020
|
+
|
|
34021
|
+
const hash1 = `${start.x},${start.y},${start.z}-${end.x},${end.y},${end.z}`;
|
|
34022
|
+
const hash2 = `${end.x},${end.y},${end.z}-${start.x},${start.y},${start.z}`; // coincident edge
|
|
34023
|
+
|
|
34024
|
+
if ( edges.has( hash1 ) === true || edges.has( hash2 ) === true ) {
|
|
34025
|
+
|
|
34026
|
+
return false;
|
|
34027
|
+
|
|
34028
|
+
} else {
|
|
34029
|
+
|
|
34030
|
+
edges.add( hash1 );
|
|
34031
|
+
edges.add( hash2 );
|
|
34032
|
+
return true;
|
|
34033
|
+
|
|
34034
|
+
}
|
|
34035
|
+
|
|
34036
|
+
}
|
|
34037
|
+
|
|
33765
34038
|
class RawShaderMaterial extends ShaderMaterial {
|
|
33766
34039
|
|
|
33767
34040
|
static get type() {
|
|
@@ -37270,6 +37543,43 @@ void main() {
|
|
|
37270
37543
|
|
|
37271
37544
|
}
|
|
37272
37545
|
|
|
37546
|
+
class InstancedBufferGeometry extends BufferGeometry {
|
|
37547
|
+
|
|
37548
|
+
constructor() {
|
|
37549
|
+
|
|
37550
|
+
super();
|
|
37551
|
+
|
|
37552
|
+
this.isInstancedBufferGeometry = true;
|
|
37553
|
+
|
|
37554
|
+
this.type = 'InstancedBufferGeometry';
|
|
37555
|
+
this.instanceCount = Infinity;
|
|
37556
|
+
|
|
37557
|
+
}
|
|
37558
|
+
|
|
37559
|
+
copy( source ) {
|
|
37560
|
+
|
|
37561
|
+
super.copy( source );
|
|
37562
|
+
|
|
37563
|
+
this.instanceCount = source.instanceCount;
|
|
37564
|
+
|
|
37565
|
+
return this;
|
|
37566
|
+
|
|
37567
|
+
}
|
|
37568
|
+
|
|
37569
|
+
toJSON() {
|
|
37570
|
+
|
|
37571
|
+
const data = super.toJSON();
|
|
37572
|
+
|
|
37573
|
+
data.instanceCount = this.instanceCount;
|
|
37574
|
+
|
|
37575
|
+
data.isInstancedBufferGeometry = true;
|
|
37576
|
+
|
|
37577
|
+
return data;
|
|
37578
|
+
|
|
37579
|
+
}
|
|
37580
|
+
|
|
37581
|
+
}
|
|
37582
|
+
|
|
37273
37583
|
class ImageBitmapLoader extends Loader {
|
|
37274
37584
|
|
|
37275
37585
|
constructor( manager ) {
|
|
@@ -39948,6 +40258,51 @@ void main() {
|
|
|
39948
40258
|
|
|
39949
40259
|
}
|
|
39950
40260
|
|
|
40261
|
+
class InstancedInterleavedBuffer extends InterleavedBuffer {
|
|
40262
|
+
|
|
40263
|
+
constructor( array, stride, meshPerAttribute = 1 ) {
|
|
40264
|
+
|
|
40265
|
+
super( array, stride );
|
|
40266
|
+
|
|
40267
|
+
this.isInstancedInterleavedBuffer = true;
|
|
40268
|
+
|
|
40269
|
+
this.meshPerAttribute = meshPerAttribute;
|
|
40270
|
+
|
|
40271
|
+
}
|
|
40272
|
+
|
|
40273
|
+
copy( source ) {
|
|
40274
|
+
|
|
40275
|
+
super.copy( source );
|
|
40276
|
+
|
|
40277
|
+
this.meshPerAttribute = source.meshPerAttribute;
|
|
40278
|
+
|
|
40279
|
+
return this;
|
|
40280
|
+
|
|
40281
|
+
}
|
|
40282
|
+
|
|
40283
|
+
clone( data ) {
|
|
40284
|
+
|
|
40285
|
+
const ib = super.clone( data );
|
|
40286
|
+
|
|
40287
|
+
ib.meshPerAttribute = this.meshPerAttribute;
|
|
40288
|
+
|
|
40289
|
+
return ib;
|
|
40290
|
+
|
|
40291
|
+
}
|
|
40292
|
+
|
|
40293
|
+
toJSON( data ) {
|
|
40294
|
+
|
|
40295
|
+
const json = super.toJSON( data );
|
|
40296
|
+
|
|
40297
|
+
json.isInstancedInterleavedBuffer = true;
|
|
40298
|
+
json.meshPerAttribute = this.meshPerAttribute;
|
|
40299
|
+
|
|
40300
|
+
return json;
|
|
40301
|
+
|
|
40302
|
+
}
|
|
40303
|
+
|
|
40304
|
+
}
|
|
40305
|
+
|
|
39951
40306
|
const _matrix = /*@__PURE__*/ new Matrix4();
|
|
39952
40307
|
|
|
39953
40308
|
class Raycaster {
|
|
@@ -40153,7 +40508,118 @@ void main() {
|
|
|
40153
40508
|
|
|
40154
40509
|
}
|
|
40155
40510
|
|
|
40156
|
-
const
|
|
40511
|
+
const _startP = /*@__PURE__*/ new Vector3();
|
|
40512
|
+
const _startEnd = /*@__PURE__*/ new Vector3();
|
|
40513
|
+
|
|
40514
|
+
class Line3 {
|
|
40515
|
+
|
|
40516
|
+
constructor( start = new Vector3(), end = new Vector3() ) {
|
|
40517
|
+
|
|
40518
|
+
this.start = start;
|
|
40519
|
+
this.end = end;
|
|
40520
|
+
|
|
40521
|
+
}
|
|
40522
|
+
|
|
40523
|
+
set( start, end ) {
|
|
40524
|
+
|
|
40525
|
+
this.start.copy( start );
|
|
40526
|
+
this.end.copy( end );
|
|
40527
|
+
|
|
40528
|
+
return this;
|
|
40529
|
+
|
|
40530
|
+
}
|
|
40531
|
+
|
|
40532
|
+
copy( line ) {
|
|
40533
|
+
|
|
40534
|
+
this.start.copy( line.start );
|
|
40535
|
+
this.end.copy( line.end );
|
|
40536
|
+
|
|
40537
|
+
return this;
|
|
40538
|
+
|
|
40539
|
+
}
|
|
40540
|
+
|
|
40541
|
+
getCenter( target ) {
|
|
40542
|
+
|
|
40543
|
+
return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 );
|
|
40544
|
+
|
|
40545
|
+
}
|
|
40546
|
+
|
|
40547
|
+
delta( target ) {
|
|
40548
|
+
|
|
40549
|
+
return target.subVectors( this.end, this.start );
|
|
40550
|
+
|
|
40551
|
+
}
|
|
40552
|
+
|
|
40553
|
+
distanceSq() {
|
|
40554
|
+
|
|
40555
|
+
return this.start.distanceToSquared( this.end );
|
|
40556
|
+
|
|
40557
|
+
}
|
|
40558
|
+
|
|
40559
|
+
distance() {
|
|
40560
|
+
|
|
40561
|
+
return this.start.distanceTo( this.end );
|
|
40562
|
+
|
|
40563
|
+
}
|
|
40564
|
+
|
|
40565
|
+
at( t, target ) {
|
|
40566
|
+
|
|
40567
|
+
return this.delta( target ).multiplyScalar( t ).add( this.start );
|
|
40568
|
+
|
|
40569
|
+
}
|
|
40570
|
+
|
|
40571
|
+
closestPointToPointParameter( point, clampToLine ) {
|
|
40572
|
+
|
|
40573
|
+
_startP.subVectors( point, this.start );
|
|
40574
|
+
_startEnd.subVectors( this.end, this.start );
|
|
40575
|
+
|
|
40576
|
+
const startEnd2 = _startEnd.dot( _startEnd );
|
|
40577
|
+
const startEnd_startP = _startEnd.dot( _startP );
|
|
40578
|
+
|
|
40579
|
+
let t = startEnd_startP / startEnd2;
|
|
40580
|
+
|
|
40581
|
+
if ( clampToLine ) {
|
|
40582
|
+
|
|
40583
|
+
t = clamp( t, 0, 1 );
|
|
40584
|
+
|
|
40585
|
+
}
|
|
40586
|
+
|
|
40587
|
+
return t;
|
|
40588
|
+
|
|
40589
|
+
}
|
|
40590
|
+
|
|
40591
|
+
closestPointToPoint( point, clampToLine, target ) {
|
|
40592
|
+
|
|
40593
|
+
const t = this.closestPointToPointParameter( point, clampToLine );
|
|
40594
|
+
|
|
40595
|
+
return this.delta( target ).multiplyScalar( t ).add( this.start );
|
|
40596
|
+
|
|
40597
|
+
}
|
|
40598
|
+
|
|
40599
|
+
applyMatrix4( matrix ) {
|
|
40600
|
+
|
|
40601
|
+
this.start.applyMatrix4( matrix );
|
|
40602
|
+
this.end.applyMatrix4( matrix );
|
|
40603
|
+
|
|
40604
|
+
return this;
|
|
40605
|
+
|
|
40606
|
+
}
|
|
40607
|
+
|
|
40608
|
+
equals( line ) {
|
|
40609
|
+
|
|
40610
|
+
return line.start.equals( this.start ) && line.end.equals( this.end );
|
|
40611
|
+
|
|
40612
|
+
}
|
|
40613
|
+
|
|
40614
|
+
clone() {
|
|
40615
|
+
|
|
40616
|
+
return new this.constructor().copy( this );
|
|
40617
|
+
|
|
40618
|
+
}
|
|
40619
|
+
|
|
40620
|
+
}
|
|
40621
|
+
|
|
40622
|
+
const _box$5 = /*@__PURE__*/ new Box3();
|
|
40157
40623
|
|
|
40158
40624
|
class BoxHelper extends LineSegments {
|
|
40159
40625
|
|
|
@@ -40187,14 +40653,14 @@ void main() {
|
|
|
40187
40653
|
|
|
40188
40654
|
if ( this.object !== undefined ) {
|
|
40189
40655
|
|
|
40190
|
-
_box.setFromObject( this.object );
|
|
40656
|
+
_box$5.setFromObject( this.object );
|
|
40191
40657
|
|
|
40192
40658
|
}
|
|
40193
40659
|
|
|
40194
|
-
if ( _box.isEmpty() ) return;
|
|
40660
|
+
if ( _box$5.isEmpty() ) return;
|
|
40195
40661
|
|
|
40196
|
-
const min = _box.min;
|
|
40197
|
-
const max = _box.max;
|
|
40662
|
+
const min = _box$5.min;
|
|
40663
|
+
const max = _box$5.max;
|
|
40198
40664
|
|
|
40199
40665
|
/*
|
|
40200
40666
|
5____4
|
|
@@ -47515,7 +47981,7 @@ void main() {
|
|
|
47515
47981
|
const _changeEvent$1 = { type: 'change' };
|
|
47516
47982
|
const _startEvent$1 = { type: 'start' };
|
|
47517
47983
|
const _endEvent$1 = { type: 'end' };
|
|
47518
|
-
const _ray = new Ray();
|
|
47984
|
+
const _ray$1 = new Ray();
|
|
47519
47985
|
const _plane = new Plane();
|
|
47520
47986
|
const _TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
|
|
47521
47987
|
|
|
@@ -47958,19 +48424,19 @@ void main() {
|
|
|
47958
48424
|
} else {
|
|
47959
48425
|
|
|
47960
48426
|
// get the ray and translation plane to compute target
|
|
47961
|
-
_ray.origin.copy( this.object.position );
|
|
47962
|
-
_ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix );
|
|
48427
|
+
_ray$1.origin.copy( this.object.position );
|
|
48428
|
+
_ray$1.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix );
|
|
47963
48429
|
|
|
47964
48430
|
// if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
|
|
47965
48431
|
// extremely large values
|
|
47966
|
-
if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) {
|
|
48432
|
+
if ( Math.abs( this.object.up.dot( _ray$1.direction ) ) < _TILT_LIMIT ) {
|
|
47967
48433
|
|
|
47968
48434
|
this.object.lookAt( this.target );
|
|
47969
48435
|
|
|
47970
48436
|
} else {
|
|
47971
48437
|
|
|
47972
48438
|
_plane.setFromNormalAndCoplanarPoint( this.object.up, this.target );
|
|
47973
|
-
_ray.intersectPlane( _plane, this.target );
|
|
48439
|
+
_ray$1.intersectPlane( _plane, this.target );
|
|
47974
48440
|
|
|
47975
48441
|
}
|
|
47976
48442
|
|
|
@@ -50331,6 +50797,169 @@ void main() {
|
|
|
50331
50797
|
}
|
|
50332
50798
|
}
|
|
50333
50799
|
|
|
50800
|
+
/**
|
|
50801
|
+
* @param {BufferGeometry} geometry
|
|
50802
|
+
* @param {number} tolerance
|
|
50803
|
+
* @return {BufferGeometry}
|
|
50804
|
+
*/
|
|
50805
|
+
function mergeVertices( geometry, tolerance = 1e-4 ) {
|
|
50806
|
+
|
|
50807
|
+
tolerance = Math.max( tolerance, Number.EPSILON );
|
|
50808
|
+
|
|
50809
|
+
// Generate an index buffer if the geometry doesn't have one, or optimize it
|
|
50810
|
+
// if it's already available.
|
|
50811
|
+
const hashToIndex = {};
|
|
50812
|
+
const indices = geometry.getIndex();
|
|
50813
|
+
const positions = geometry.getAttribute( 'position' );
|
|
50814
|
+
const vertexCount = indices ? indices.count : positions.count;
|
|
50815
|
+
|
|
50816
|
+
// next value for triangle indices
|
|
50817
|
+
let nextIndex = 0;
|
|
50818
|
+
|
|
50819
|
+
// attributes and new attribute arrays
|
|
50820
|
+
const attributeNames = Object.keys( geometry.attributes );
|
|
50821
|
+
const tmpAttributes = {};
|
|
50822
|
+
const tmpMorphAttributes = {};
|
|
50823
|
+
const newIndices = [];
|
|
50824
|
+
const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
|
|
50825
|
+
const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
|
|
50826
|
+
|
|
50827
|
+
// Initialize the arrays, allocating space conservatively. Extra
|
|
50828
|
+
// space will be trimmed in the last step.
|
|
50829
|
+
for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
|
|
50830
|
+
|
|
50831
|
+
const name = attributeNames[ i ];
|
|
50832
|
+
const attr = geometry.attributes[ name ];
|
|
50833
|
+
|
|
50834
|
+
tmpAttributes[ name ] = new attr.constructor(
|
|
50835
|
+
new attr.array.constructor( attr.count * attr.itemSize ),
|
|
50836
|
+
attr.itemSize,
|
|
50837
|
+
attr.normalized
|
|
50838
|
+
);
|
|
50839
|
+
|
|
50840
|
+
const morphAttributes = geometry.morphAttributes[ name ];
|
|
50841
|
+
if ( morphAttributes ) {
|
|
50842
|
+
|
|
50843
|
+
if ( ! tmpMorphAttributes[ name ] ) tmpMorphAttributes[ name ] = [];
|
|
50844
|
+
morphAttributes.forEach( ( morphAttr, i ) => {
|
|
50845
|
+
|
|
50846
|
+
const array = new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize );
|
|
50847
|
+
tmpMorphAttributes[ name ][ i ] = new morphAttr.constructor( array, morphAttr.itemSize, morphAttr.normalized );
|
|
50848
|
+
|
|
50849
|
+
} );
|
|
50850
|
+
|
|
50851
|
+
}
|
|
50852
|
+
|
|
50853
|
+
}
|
|
50854
|
+
|
|
50855
|
+
// convert the error tolerance to an amount of decimal places to truncate to
|
|
50856
|
+
const halfTolerance = tolerance * 0.5;
|
|
50857
|
+
const exponent = Math.log10( 1 / tolerance );
|
|
50858
|
+
const hashMultiplier = Math.pow( 10, exponent );
|
|
50859
|
+
const hashAdditive = halfTolerance * hashMultiplier;
|
|
50860
|
+
for ( let i = 0; i < vertexCount; i ++ ) {
|
|
50861
|
+
|
|
50862
|
+
const index = indices ? indices.getX( i ) : i;
|
|
50863
|
+
|
|
50864
|
+
// Generate a hash for the vertex attributes at the current index 'i'
|
|
50865
|
+
let hash = '';
|
|
50866
|
+
for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
|
|
50867
|
+
|
|
50868
|
+
const name = attributeNames[ j ];
|
|
50869
|
+
const attribute = geometry.getAttribute( name );
|
|
50870
|
+
const itemSize = attribute.itemSize;
|
|
50871
|
+
|
|
50872
|
+
for ( let k = 0; k < itemSize; k ++ ) {
|
|
50873
|
+
|
|
50874
|
+
// double tilde truncates the decimal value
|
|
50875
|
+
hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * hashMultiplier + hashAdditive ) },`;
|
|
50876
|
+
|
|
50877
|
+
}
|
|
50878
|
+
|
|
50879
|
+
}
|
|
50880
|
+
|
|
50881
|
+
// Add another reference to the vertex if it's already
|
|
50882
|
+
// used by another index
|
|
50883
|
+
if ( hash in hashToIndex ) {
|
|
50884
|
+
|
|
50885
|
+
newIndices.push( hashToIndex[ hash ] );
|
|
50886
|
+
|
|
50887
|
+
} else {
|
|
50888
|
+
|
|
50889
|
+
// copy data to the new index in the temporary attributes
|
|
50890
|
+
for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
|
|
50891
|
+
|
|
50892
|
+
const name = attributeNames[ j ];
|
|
50893
|
+
const attribute = geometry.getAttribute( name );
|
|
50894
|
+
const morphAttributes = geometry.morphAttributes[ name ];
|
|
50895
|
+
const itemSize = attribute.itemSize;
|
|
50896
|
+
const newArray = tmpAttributes[ name ];
|
|
50897
|
+
const newMorphArrays = tmpMorphAttributes[ name ];
|
|
50898
|
+
|
|
50899
|
+
for ( let k = 0; k < itemSize; k ++ ) {
|
|
50900
|
+
|
|
50901
|
+
const getterFunc = getters[ k ];
|
|
50902
|
+
const setterFunc = setters[ k ];
|
|
50903
|
+
newArray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) );
|
|
50904
|
+
|
|
50905
|
+
if ( morphAttributes ) {
|
|
50906
|
+
|
|
50907
|
+
for ( let m = 0, ml = morphAttributes.length; m < ml; m ++ ) {
|
|
50908
|
+
|
|
50909
|
+
newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttributes[ m ][ getterFunc ]( index ) );
|
|
50910
|
+
|
|
50911
|
+
}
|
|
50912
|
+
|
|
50913
|
+
}
|
|
50914
|
+
|
|
50915
|
+
}
|
|
50916
|
+
|
|
50917
|
+
}
|
|
50918
|
+
|
|
50919
|
+
hashToIndex[ hash ] = nextIndex;
|
|
50920
|
+
newIndices.push( nextIndex );
|
|
50921
|
+
nextIndex ++;
|
|
50922
|
+
|
|
50923
|
+
}
|
|
50924
|
+
|
|
50925
|
+
}
|
|
50926
|
+
|
|
50927
|
+
// generate result BufferGeometry
|
|
50928
|
+
const result = geometry.clone();
|
|
50929
|
+
for ( const name in geometry.attributes ) {
|
|
50930
|
+
|
|
50931
|
+
const tmpAttribute = tmpAttributes[ name ];
|
|
50932
|
+
|
|
50933
|
+
result.setAttribute( name, new tmpAttribute.constructor(
|
|
50934
|
+
tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ),
|
|
50935
|
+
tmpAttribute.itemSize,
|
|
50936
|
+
tmpAttribute.normalized,
|
|
50937
|
+
) );
|
|
50938
|
+
|
|
50939
|
+
if ( ! ( name in tmpMorphAttributes ) ) continue;
|
|
50940
|
+
|
|
50941
|
+
for ( let j = 0; j < tmpMorphAttributes[ name ].length; j ++ ) {
|
|
50942
|
+
|
|
50943
|
+
const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ];
|
|
50944
|
+
|
|
50945
|
+
result.morphAttributes[ name ][ j ] = new tmpMorphAttribute.constructor(
|
|
50946
|
+
tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ),
|
|
50947
|
+
tmpMorphAttribute.itemSize,
|
|
50948
|
+
tmpMorphAttribute.normalized,
|
|
50949
|
+
);
|
|
50950
|
+
|
|
50951
|
+
}
|
|
50952
|
+
|
|
50953
|
+
}
|
|
50954
|
+
|
|
50955
|
+
// indices
|
|
50956
|
+
|
|
50957
|
+
result.setIndex( newIndices );
|
|
50958
|
+
|
|
50959
|
+
return result;
|
|
50960
|
+
|
|
50961
|
+
}
|
|
50962
|
+
|
|
50334
50963
|
/**
|
|
50335
50964
|
* @param {BufferGeometry} geometry
|
|
50336
50965
|
* @param {number} drawMode
|
|
@@ -58889,6 +59518,2188 @@ void main() {
|
|
|
58889
59518
|
|
|
58890
59519
|
}
|
|
58891
59520
|
|
|
59521
|
+
const _inv = new Matrix4();
|
|
59522
|
+
/** 世界系命中点 → local 顶点(位置 worldToLocal,法线用逆变换)。 */
|
|
59523
|
+
function worldHitToLocalVertex(h, mesh) {
|
|
59524
|
+
const lp = mesh.worldToLocal(h.point.clone());
|
|
59525
|
+
_inv.copy(mesh.matrixWorld).invert();
|
|
59526
|
+
const ln = h.normal.clone().transformDirection(_inv).normalize();
|
|
59527
|
+
return {
|
|
59528
|
+
x: lp.x,
|
|
59529
|
+
y: lp.y,
|
|
59530
|
+
z: lp.z,
|
|
59531
|
+
nx: ln.x,
|
|
59532
|
+
ny: ln.y,
|
|
59533
|
+
nz: ln.z,
|
|
59534
|
+
faceIndex: h.faceIndex,
|
|
59535
|
+
};
|
|
59536
|
+
}
|
|
59537
|
+
const _nm = new Matrix3();
|
|
59538
|
+
/** local 顶点 → world 位置 + world 法线(供渲染)。 */
|
|
59539
|
+
function localVertexToWorld(v, mesh) {
|
|
59540
|
+
const p = new Vector3(v.x, v.y, v.z).applyMatrix4(mesh.matrixWorld);
|
|
59541
|
+
_nm.getNormalMatrix(mesh.matrixWorld);
|
|
59542
|
+
const n = new Vector3(v.nx, v.ny, v.nz).applyMatrix3(_nm).normalize();
|
|
59543
|
+
return { p, n };
|
|
59544
|
+
}
|
|
59545
|
+
|
|
59546
|
+
const ray = new Raycaster();
|
|
59547
|
+
const ndc = new Vector2();
|
|
59548
|
+
/**
|
|
59549
|
+
* 把屏幕坐标(clientX/Y)投射到 mesh 表面,返回命中点、world-space 法线与 faceIndex。
|
|
59550
|
+
* 未命中返回 null。本地实现,不依赖 Copper3D 未导出的 raycast 内部函数。
|
|
59551
|
+
*/
|
|
59552
|
+
function raycastSurface(camera, container, mesh, clientX, clientY) {
|
|
59553
|
+
var _a;
|
|
59554
|
+
const rect = container.getBoundingClientRect();
|
|
59555
|
+
ndc.x = ((clientX - rect.left) / rect.width) * 2 - 1;
|
|
59556
|
+
ndc.y = -((clientY - rect.top) / rect.height) * 2 + 1;
|
|
59557
|
+
ray.setFromCamera(ndc, camera);
|
|
59558
|
+
const hits = ray.intersectObject(mesh, false);
|
|
59559
|
+
if (hits.length === 0)
|
|
59560
|
+
return null;
|
|
59561
|
+
const h = hits[0];
|
|
59562
|
+
const point = h.point.clone();
|
|
59563
|
+
const normal = new Vector3(0, 0, 1);
|
|
59564
|
+
if (h.face) {
|
|
59565
|
+
normal.copy(h.face.normal);
|
|
59566
|
+
const nm = new Matrix3().getNormalMatrix(mesh.matrixWorld);
|
|
59567
|
+
normal.applyMatrix3(nm).normalize();
|
|
59568
|
+
}
|
|
59569
|
+
return { point, normal, faceIndex: (_a = h.faceIndex) !== null && _a !== void 0 ? _a : -1 };
|
|
59570
|
+
}
|
|
59571
|
+
|
|
59572
|
+
/**
|
|
59573
|
+
* 在 local 顶点处生成一个标记小球(fiducial)。local→world 后沿 world 法线略外移,
|
|
59574
|
+
* 避免一半埋进表面。radius 由调用方按模型 bbox 缩放传入。
|
|
59575
|
+
*/
|
|
59576
|
+
function makePointMarker(v, mesh, color, radius) {
|
|
59577
|
+
const geo = new SphereGeometry(radius, 16, 16);
|
|
59578
|
+
const mat = new MeshBasicMaterial({ color });
|
|
59579
|
+
const m = new Mesh(geo, mat);
|
|
59580
|
+
const { p, n } = localVertexToWorld(v, mesh);
|
|
59581
|
+
m.position.copy(p.addScaledVector(n, radius * 0.5));
|
|
59582
|
+
m.renderOrder = 999;
|
|
59583
|
+
return m;
|
|
59584
|
+
}
|
|
59585
|
+
|
|
59586
|
+
/**
|
|
59587
|
+
* 模式 A(自由手绘)一笔的状态机。
|
|
59588
|
+
* pointermove 时不断 addSample;与上一采样点世界距离 < minGap 的样本丢弃,避免过密。
|
|
59589
|
+
* 顶点以 local 存(由 mesh 把世界命中点转 local)。
|
|
59590
|
+
*/
|
|
59591
|
+
class StrokeContour {
|
|
59592
|
+
/**
|
|
59593
|
+
* @param minGap 采样最小间距(世界系),去抖
|
|
59594
|
+
* @param maxJump 跳变阈值(世界系):距离 > maxJump 且法线大幅翻转时丢弃该样本,
|
|
59595
|
+
* 避免笔触掠过沟缝/轮廓边时相邻点落在不同深度、直线段横穿模型。
|
|
59596
|
+
*/
|
|
59597
|
+
constructor(minGap, maxJump, mesh) {
|
|
59598
|
+
this.minGap = minGap;
|
|
59599
|
+
this.maxJump = maxJump;
|
|
59600
|
+
this.mesh = mesh;
|
|
59601
|
+
this.verts = [];
|
|
59602
|
+
this.last = new Vector3();
|
|
59603
|
+
this.lastNormal = new Vector3();
|
|
59604
|
+
this.has = false;
|
|
59605
|
+
}
|
|
59606
|
+
begin() {
|
|
59607
|
+
this.verts = [];
|
|
59608
|
+
this.has = false;
|
|
59609
|
+
}
|
|
59610
|
+
addSample(hit) {
|
|
59611
|
+
if (this.has) {
|
|
59612
|
+
const d = hit.point.distanceTo(this.last);
|
|
59613
|
+
if (d < this.minGap)
|
|
59614
|
+
return; // 太近,去抖
|
|
59615
|
+
// 跳变剔除:距离突然很大「且」法线大幅翻转(>~75°)→ 多半跳到了背面/远面,丢弃,
|
|
59616
|
+
// 否则相邻两点之间的直线段会横穿模型内部。两个条件同时满足才剔除:
|
|
59617
|
+
// 快速画(距离大、法线相近)不误删;画过尖锐棱边(法线变、距离近)也不误删。
|
|
59618
|
+
if (d > this.maxJump && hit.normal.dot(this.lastNormal) < 0.25)
|
|
59619
|
+
return;
|
|
59620
|
+
}
|
|
59621
|
+
this.verts.push(worldHitToLocalVertex(hit, this.mesh));
|
|
59622
|
+
this.last.copy(hit.point);
|
|
59623
|
+
this.lastNormal.copy(hit.normal);
|
|
59624
|
+
this.has = true;
|
|
59625
|
+
}
|
|
59626
|
+
get vertices() {
|
|
59627
|
+
return this.verts;
|
|
59628
|
+
}
|
|
59629
|
+
end() {
|
|
59630
|
+
return this.verts;
|
|
59631
|
+
}
|
|
59632
|
+
}
|
|
59633
|
+
|
|
59634
|
+
const _box$1 = new Box3();
|
|
59635
|
+
const _vector = new Vector3();
|
|
59636
|
+
|
|
59637
|
+
class LineSegmentsGeometry extends InstancedBufferGeometry {
|
|
59638
|
+
|
|
59639
|
+
constructor() {
|
|
59640
|
+
|
|
59641
|
+
super();
|
|
59642
|
+
|
|
59643
|
+
this.isLineSegmentsGeometry = true;
|
|
59644
|
+
|
|
59645
|
+
this.type = 'LineSegmentsGeometry';
|
|
59646
|
+
|
|
59647
|
+
const positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
|
|
59648
|
+
const uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
|
|
59649
|
+
const index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
|
|
59650
|
+
|
|
59651
|
+
this.setIndex( index );
|
|
59652
|
+
this.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
|
|
59653
|
+
this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
|
|
59654
|
+
|
|
59655
|
+
}
|
|
59656
|
+
|
|
59657
|
+
applyMatrix4( matrix ) {
|
|
59658
|
+
|
|
59659
|
+
const start = this.attributes.instanceStart;
|
|
59660
|
+
const end = this.attributes.instanceEnd;
|
|
59661
|
+
|
|
59662
|
+
if ( start !== undefined ) {
|
|
59663
|
+
|
|
59664
|
+
start.applyMatrix4( matrix );
|
|
59665
|
+
|
|
59666
|
+
end.applyMatrix4( matrix );
|
|
59667
|
+
|
|
59668
|
+
start.needsUpdate = true;
|
|
59669
|
+
|
|
59670
|
+
}
|
|
59671
|
+
|
|
59672
|
+
if ( this.boundingBox !== null ) {
|
|
59673
|
+
|
|
59674
|
+
this.computeBoundingBox();
|
|
59675
|
+
|
|
59676
|
+
}
|
|
59677
|
+
|
|
59678
|
+
if ( this.boundingSphere !== null ) {
|
|
59679
|
+
|
|
59680
|
+
this.computeBoundingSphere();
|
|
59681
|
+
|
|
59682
|
+
}
|
|
59683
|
+
|
|
59684
|
+
return this;
|
|
59685
|
+
|
|
59686
|
+
}
|
|
59687
|
+
|
|
59688
|
+
setPositions( array ) {
|
|
59689
|
+
|
|
59690
|
+
let lineSegments;
|
|
59691
|
+
|
|
59692
|
+
if ( array instanceof Float32Array ) {
|
|
59693
|
+
|
|
59694
|
+
lineSegments = array;
|
|
59695
|
+
|
|
59696
|
+
} else if ( Array.isArray( array ) ) {
|
|
59697
|
+
|
|
59698
|
+
lineSegments = new Float32Array( array );
|
|
59699
|
+
|
|
59700
|
+
}
|
|
59701
|
+
|
|
59702
|
+
const instanceBuffer = new InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
|
|
59703
|
+
|
|
59704
|
+
this.setAttribute( 'instanceStart', new InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
|
|
59705
|
+
this.setAttribute( 'instanceEnd', new InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
|
|
59706
|
+
|
|
59707
|
+
this.instanceCount = this.attributes.instanceStart.count;
|
|
59708
|
+
|
|
59709
|
+
//
|
|
59710
|
+
|
|
59711
|
+
this.computeBoundingBox();
|
|
59712
|
+
this.computeBoundingSphere();
|
|
59713
|
+
|
|
59714
|
+
return this;
|
|
59715
|
+
|
|
59716
|
+
}
|
|
59717
|
+
|
|
59718
|
+
setColors( array ) {
|
|
59719
|
+
|
|
59720
|
+
let colors;
|
|
59721
|
+
|
|
59722
|
+
if ( array instanceof Float32Array ) {
|
|
59723
|
+
|
|
59724
|
+
colors = array;
|
|
59725
|
+
|
|
59726
|
+
} else if ( Array.isArray( array ) ) {
|
|
59727
|
+
|
|
59728
|
+
colors = new Float32Array( array );
|
|
59729
|
+
|
|
59730
|
+
}
|
|
59731
|
+
|
|
59732
|
+
const instanceColorBuffer = new InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
|
|
59733
|
+
|
|
59734
|
+
this.setAttribute( 'instanceColorStart', new InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
|
|
59735
|
+
this.setAttribute( 'instanceColorEnd', new InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
|
|
59736
|
+
|
|
59737
|
+
return this;
|
|
59738
|
+
|
|
59739
|
+
}
|
|
59740
|
+
|
|
59741
|
+
fromWireframeGeometry( geometry ) {
|
|
59742
|
+
|
|
59743
|
+
this.setPositions( geometry.attributes.position.array );
|
|
59744
|
+
|
|
59745
|
+
return this;
|
|
59746
|
+
|
|
59747
|
+
}
|
|
59748
|
+
|
|
59749
|
+
fromEdgesGeometry( geometry ) {
|
|
59750
|
+
|
|
59751
|
+
this.setPositions( geometry.attributes.position.array );
|
|
59752
|
+
|
|
59753
|
+
return this;
|
|
59754
|
+
|
|
59755
|
+
}
|
|
59756
|
+
|
|
59757
|
+
fromMesh( mesh ) {
|
|
59758
|
+
|
|
59759
|
+
this.fromWireframeGeometry( new WireframeGeometry( mesh.geometry ) );
|
|
59760
|
+
|
|
59761
|
+
// set colors, maybe
|
|
59762
|
+
|
|
59763
|
+
return this;
|
|
59764
|
+
|
|
59765
|
+
}
|
|
59766
|
+
|
|
59767
|
+
fromLineSegments( lineSegments ) {
|
|
59768
|
+
|
|
59769
|
+
const geometry = lineSegments.geometry;
|
|
59770
|
+
|
|
59771
|
+
this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
|
|
59772
|
+
|
|
59773
|
+
// set colors, maybe
|
|
59774
|
+
|
|
59775
|
+
return this;
|
|
59776
|
+
|
|
59777
|
+
}
|
|
59778
|
+
|
|
59779
|
+
computeBoundingBox() {
|
|
59780
|
+
|
|
59781
|
+
if ( this.boundingBox === null ) {
|
|
59782
|
+
|
|
59783
|
+
this.boundingBox = new Box3();
|
|
59784
|
+
|
|
59785
|
+
}
|
|
59786
|
+
|
|
59787
|
+
const start = this.attributes.instanceStart;
|
|
59788
|
+
const end = this.attributes.instanceEnd;
|
|
59789
|
+
|
|
59790
|
+
if ( start !== undefined && end !== undefined ) {
|
|
59791
|
+
|
|
59792
|
+
this.boundingBox.setFromBufferAttribute( start );
|
|
59793
|
+
|
|
59794
|
+
_box$1.setFromBufferAttribute( end );
|
|
59795
|
+
|
|
59796
|
+
this.boundingBox.union( _box$1 );
|
|
59797
|
+
|
|
59798
|
+
}
|
|
59799
|
+
|
|
59800
|
+
}
|
|
59801
|
+
|
|
59802
|
+
computeBoundingSphere() {
|
|
59803
|
+
|
|
59804
|
+
if ( this.boundingSphere === null ) {
|
|
59805
|
+
|
|
59806
|
+
this.boundingSphere = new Sphere();
|
|
59807
|
+
|
|
59808
|
+
}
|
|
59809
|
+
|
|
59810
|
+
if ( this.boundingBox === null ) {
|
|
59811
|
+
|
|
59812
|
+
this.computeBoundingBox();
|
|
59813
|
+
|
|
59814
|
+
}
|
|
59815
|
+
|
|
59816
|
+
const start = this.attributes.instanceStart;
|
|
59817
|
+
const end = this.attributes.instanceEnd;
|
|
59818
|
+
|
|
59819
|
+
if ( start !== undefined && end !== undefined ) {
|
|
59820
|
+
|
|
59821
|
+
const center = this.boundingSphere.center;
|
|
59822
|
+
|
|
59823
|
+
this.boundingBox.getCenter( center );
|
|
59824
|
+
|
|
59825
|
+
let maxRadiusSq = 0;
|
|
59826
|
+
|
|
59827
|
+
for ( let i = 0, il = start.count; i < il; i ++ ) {
|
|
59828
|
+
|
|
59829
|
+
_vector.fromBufferAttribute( start, i );
|
|
59830
|
+
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) );
|
|
59831
|
+
|
|
59832
|
+
_vector.fromBufferAttribute( end, i );
|
|
59833
|
+
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) );
|
|
59834
|
+
|
|
59835
|
+
}
|
|
59836
|
+
|
|
59837
|
+
this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
|
|
59838
|
+
|
|
59839
|
+
if ( isNaN( this.boundingSphere.radius ) ) {
|
|
59840
|
+
|
|
59841
|
+
console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
|
|
59842
|
+
|
|
59843
|
+
}
|
|
59844
|
+
|
|
59845
|
+
}
|
|
59846
|
+
|
|
59847
|
+
}
|
|
59848
|
+
|
|
59849
|
+
toJSON() {
|
|
59850
|
+
|
|
59851
|
+
// todo
|
|
59852
|
+
|
|
59853
|
+
}
|
|
59854
|
+
|
|
59855
|
+
applyMatrix( matrix ) {
|
|
59856
|
+
|
|
59857
|
+
console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' );
|
|
59858
|
+
|
|
59859
|
+
return this.applyMatrix4( matrix );
|
|
59860
|
+
|
|
59861
|
+
}
|
|
59862
|
+
|
|
59863
|
+
}
|
|
59864
|
+
|
|
59865
|
+
UniformsLib.line = {
|
|
59866
|
+
|
|
59867
|
+
worldUnits: { value: 1 },
|
|
59868
|
+
linewidth: { value: 1 },
|
|
59869
|
+
resolution: { value: new Vector2( 1, 1 ) },
|
|
59870
|
+
dashOffset: { value: 0 },
|
|
59871
|
+
dashScale: { value: 1 },
|
|
59872
|
+
dashSize: { value: 1 },
|
|
59873
|
+
gapSize: { value: 1 } // todo FIX - maybe change to totalSize
|
|
59874
|
+
|
|
59875
|
+
};
|
|
59876
|
+
|
|
59877
|
+
ShaderLib[ 'line' ] = {
|
|
59878
|
+
|
|
59879
|
+
uniforms: UniformsUtils.merge( [
|
|
59880
|
+
UniformsLib.common,
|
|
59881
|
+
UniformsLib.fog,
|
|
59882
|
+
UniformsLib.line
|
|
59883
|
+
] ),
|
|
59884
|
+
|
|
59885
|
+
vertexShader:
|
|
59886
|
+
/* glsl */`
|
|
59887
|
+
#include <common>
|
|
59888
|
+
#include <color_pars_vertex>
|
|
59889
|
+
#include <fog_pars_vertex>
|
|
59890
|
+
#include <logdepthbuf_pars_vertex>
|
|
59891
|
+
#include <clipping_planes_pars_vertex>
|
|
59892
|
+
|
|
59893
|
+
uniform float linewidth;
|
|
59894
|
+
uniform vec2 resolution;
|
|
59895
|
+
|
|
59896
|
+
attribute vec3 instanceStart;
|
|
59897
|
+
attribute vec3 instanceEnd;
|
|
59898
|
+
|
|
59899
|
+
attribute vec3 instanceColorStart;
|
|
59900
|
+
attribute vec3 instanceColorEnd;
|
|
59901
|
+
|
|
59902
|
+
#ifdef WORLD_UNITS
|
|
59903
|
+
|
|
59904
|
+
varying vec4 worldPos;
|
|
59905
|
+
varying vec3 worldStart;
|
|
59906
|
+
varying vec3 worldEnd;
|
|
59907
|
+
|
|
59908
|
+
#ifdef USE_DASH
|
|
59909
|
+
|
|
59910
|
+
varying vec2 vUv;
|
|
59911
|
+
|
|
59912
|
+
#endif
|
|
59913
|
+
|
|
59914
|
+
#else
|
|
59915
|
+
|
|
59916
|
+
varying vec2 vUv;
|
|
59917
|
+
|
|
59918
|
+
#endif
|
|
59919
|
+
|
|
59920
|
+
#ifdef USE_DASH
|
|
59921
|
+
|
|
59922
|
+
uniform float dashScale;
|
|
59923
|
+
attribute float instanceDistanceStart;
|
|
59924
|
+
attribute float instanceDistanceEnd;
|
|
59925
|
+
varying float vLineDistance;
|
|
59926
|
+
|
|
59927
|
+
#endif
|
|
59928
|
+
|
|
59929
|
+
void trimSegment( const in vec4 start, inout vec4 end ) {
|
|
59930
|
+
|
|
59931
|
+
// trim end segment so it terminates between the camera plane and the near plane
|
|
59932
|
+
|
|
59933
|
+
// conservative estimate of the near plane
|
|
59934
|
+
float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
|
|
59935
|
+
float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
|
|
59936
|
+
float nearEstimate = - 0.5 * b / a;
|
|
59937
|
+
|
|
59938
|
+
float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
|
|
59939
|
+
|
|
59940
|
+
end.xyz = mix( start.xyz, end.xyz, alpha );
|
|
59941
|
+
|
|
59942
|
+
}
|
|
59943
|
+
|
|
59944
|
+
void main() {
|
|
59945
|
+
|
|
59946
|
+
#ifdef USE_COLOR
|
|
59947
|
+
|
|
59948
|
+
vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
|
|
59949
|
+
|
|
59950
|
+
#endif
|
|
59951
|
+
|
|
59952
|
+
#ifdef USE_DASH
|
|
59953
|
+
|
|
59954
|
+
vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
|
|
59955
|
+
vUv = uv;
|
|
59956
|
+
|
|
59957
|
+
#endif
|
|
59958
|
+
|
|
59959
|
+
float aspect = resolution.x / resolution.y;
|
|
59960
|
+
|
|
59961
|
+
// camera space
|
|
59962
|
+
vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
|
|
59963
|
+
vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
|
|
59964
|
+
|
|
59965
|
+
#ifdef WORLD_UNITS
|
|
59966
|
+
|
|
59967
|
+
worldStart = start.xyz;
|
|
59968
|
+
worldEnd = end.xyz;
|
|
59969
|
+
|
|
59970
|
+
#else
|
|
59971
|
+
|
|
59972
|
+
vUv = uv;
|
|
59973
|
+
|
|
59974
|
+
#endif
|
|
59975
|
+
|
|
59976
|
+
// special case for perspective projection, and segments that terminate either in, or behind, the camera plane
|
|
59977
|
+
// clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
|
|
59978
|
+
// but we need to perform ndc-space calculations in the shader, so we must address this issue directly
|
|
59979
|
+
// perhaps there is a more elegant solution -- WestLangley
|
|
59980
|
+
|
|
59981
|
+
bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
|
|
59982
|
+
|
|
59983
|
+
if ( perspective ) {
|
|
59984
|
+
|
|
59985
|
+
if ( start.z < 0.0 && end.z >= 0.0 ) {
|
|
59986
|
+
|
|
59987
|
+
trimSegment( start, end );
|
|
59988
|
+
|
|
59989
|
+
} else if ( end.z < 0.0 && start.z >= 0.0 ) {
|
|
59990
|
+
|
|
59991
|
+
trimSegment( end, start );
|
|
59992
|
+
|
|
59993
|
+
}
|
|
59994
|
+
|
|
59995
|
+
}
|
|
59996
|
+
|
|
59997
|
+
// clip space
|
|
59998
|
+
vec4 clipStart = projectionMatrix * start;
|
|
59999
|
+
vec4 clipEnd = projectionMatrix * end;
|
|
60000
|
+
|
|
60001
|
+
// ndc space
|
|
60002
|
+
vec3 ndcStart = clipStart.xyz / clipStart.w;
|
|
60003
|
+
vec3 ndcEnd = clipEnd.xyz / clipEnd.w;
|
|
60004
|
+
|
|
60005
|
+
// direction
|
|
60006
|
+
vec2 dir = ndcEnd.xy - ndcStart.xy;
|
|
60007
|
+
|
|
60008
|
+
// account for clip-space aspect ratio
|
|
60009
|
+
dir.x *= aspect;
|
|
60010
|
+
dir = normalize( dir );
|
|
60011
|
+
|
|
60012
|
+
#ifdef WORLD_UNITS
|
|
60013
|
+
|
|
60014
|
+
vec3 worldDir = normalize( end.xyz - start.xyz );
|
|
60015
|
+
vec3 tmpFwd = normalize( mix( start.xyz, end.xyz, 0.5 ) );
|
|
60016
|
+
vec3 worldUp = normalize( cross( worldDir, tmpFwd ) );
|
|
60017
|
+
vec3 worldFwd = cross( worldDir, worldUp );
|
|
60018
|
+
worldPos = position.y < 0.5 ? start: end;
|
|
60019
|
+
|
|
60020
|
+
// height offset
|
|
60021
|
+
float hw = linewidth * 0.5;
|
|
60022
|
+
worldPos.xyz += position.x < 0.0 ? hw * worldUp : - hw * worldUp;
|
|
60023
|
+
|
|
60024
|
+
// don't extend the line if we're rendering dashes because we
|
|
60025
|
+
// won't be rendering the endcaps
|
|
60026
|
+
#ifndef USE_DASH
|
|
60027
|
+
|
|
60028
|
+
// cap extension
|
|
60029
|
+
worldPos.xyz += position.y < 0.5 ? - hw * worldDir : hw * worldDir;
|
|
60030
|
+
|
|
60031
|
+
// add width to the box
|
|
60032
|
+
worldPos.xyz += worldFwd * hw;
|
|
60033
|
+
|
|
60034
|
+
// endcaps
|
|
60035
|
+
if ( position.y > 1.0 || position.y < 0.0 ) {
|
|
60036
|
+
|
|
60037
|
+
worldPos.xyz -= worldFwd * 2.0 * hw;
|
|
60038
|
+
|
|
60039
|
+
}
|
|
60040
|
+
|
|
60041
|
+
#endif
|
|
60042
|
+
|
|
60043
|
+
// project the worldpos
|
|
60044
|
+
vec4 clip = projectionMatrix * worldPos;
|
|
60045
|
+
|
|
60046
|
+
// shift the depth of the projected points so the line
|
|
60047
|
+
// segments overlap neatly
|
|
60048
|
+
vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd;
|
|
60049
|
+
clip.z = clipPose.z * clip.w;
|
|
60050
|
+
|
|
60051
|
+
#else
|
|
60052
|
+
|
|
60053
|
+
vec2 offset = vec2( dir.y, - dir.x );
|
|
60054
|
+
// undo aspect ratio adjustment
|
|
60055
|
+
dir.x /= aspect;
|
|
60056
|
+
offset.x /= aspect;
|
|
60057
|
+
|
|
60058
|
+
// sign flip
|
|
60059
|
+
if ( position.x < 0.0 ) offset *= - 1.0;
|
|
60060
|
+
|
|
60061
|
+
// endcaps
|
|
60062
|
+
if ( position.y < 0.0 ) {
|
|
60063
|
+
|
|
60064
|
+
offset += - dir;
|
|
60065
|
+
|
|
60066
|
+
} else if ( position.y > 1.0 ) {
|
|
60067
|
+
|
|
60068
|
+
offset += dir;
|
|
60069
|
+
|
|
60070
|
+
}
|
|
60071
|
+
|
|
60072
|
+
// adjust for linewidth
|
|
60073
|
+
offset *= linewidth;
|
|
60074
|
+
|
|
60075
|
+
// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
|
|
60076
|
+
offset /= resolution.y;
|
|
60077
|
+
|
|
60078
|
+
// select end
|
|
60079
|
+
vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
|
|
60080
|
+
|
|
60081
|
+
// back to clip space
|
|
60082
|
+
offset *= clip.w;
|
|
60083
|
+
|
|
60084
|
+
clip.xy += offset;
|
|
60085
|
+
|
|
60086
|
+
#endif
|
|
60087
|
+
|
|
60088
|
+
gl_Position = clip;
|
|
60089
|
+
|
|
60090
|
+
vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation
|
|
60091
|
+
|
|
60092
|
+
#include <logdepthbuf_vertex>
|
|
60093
|
+
#include <clipping_planes_vertex>
|
|
60094
|
+
#include <fog_vertex>
|
|
60095
|
+
|
|
60096
|
+
}
|
|
60097
|
+
`,
|
|
60098
|
+
|
|
60099
|
+
fragmentShader:
|
|
60100
|
+
/* glsl */`
|
|
60101
|
+
uniform vec3 diffuse;
|
|
60102
|
+
uniform float opacity;
|
|
60103
|
+
uniform float linewidth;
|
|
60104
|
+
|
|
60105
|
+
#ifdef USE_DASH
|
|
60106
|
+
|
|
60107
|
+
uniform float dashOffset;
|
|
60108
|
+
uniform float dashSize;
|
|
60109
|
+
uniform float gapSize;
|
|
60110
|
+
|
|
60111
|
+
#endif
|
|
60112
|
+
|
|
60113
|
+
varying float vLineDistance;
|
|
60114
|
+
|
|
60115
|
+
#ifdef WORLD_UNITS
|
|
60116
|
+
|
|
60117
|
+
varying vec4 worldPos;
|
|
60118
|
+
varying vec3 worldStart;
|
|
60119
|
+
varying vec3 worldEnd;
|
|
60120
|
+
|
|
60121
|
+
#ifdef USE_DASH
|
|
60122
|
+
|
|
60123
|
+
varying vec2 vUv;
|
|
60124
|
+
|
|
60125
|
+
#endif
|
|
60126
|
+
|
|
60127
|
+
#else
|
|
60128
|
+
|
|
60129
|
+
varying vec2 vUv;
|
|
60130
|
+
|
|
60131
|
+
#endif
|
|
60132
|
+
|
|
60133
|
+
#include <common>
|
|
60134
|
+
#include <color_pars_fragment>
|
|
60135
|
+
#include <fog_pars_fragment>
|
|
60136
|
+
#include <logdepthbuf_pars_fragment>
|
|
60137
|
+
#include <clipping_planes_pars_fragment>
|
|
60138
|
+
|
|
60139
|
+
vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) {
|
|
60140
|
+
|
|
60141
|
+
float mua;
|
|
60142
|
+
float mub;
|
|
60143
|
+
|
|
60144
|
+
vec3 p13 = p1 - p3;
|
|
60145
|
+
vec3 p43 = p4 - p3;
|
|
60146
|
+
|
|
60147
|
+
vec3 p21 = p2 - p1;
|
|
60148
|
+
|
|
60149
|
+
float d1343 = dot( p13, p43 );
|
|
60150
|
+
float d4321 = dot( p43, p21 );
|
|
60151
|
+
float d1321 = dot( p13, p21 );
|
|
60152
|
+
float d4343 = dot( p43, p43 );
|
|
60153
|
+
float d2121 = dot( p21, p21 );
|
|
60154
|
+
|
|
60155
|
+
float denom = d2121 * d4343 - d4321 * d4321;
|
|
60156
|
+
|
|
60157
|
+
float numer = d1343 * d4321 - d1321 * d4343;
|
|
60158
|
+
|
|
60159
|
+
mua = numer / denom;
|
|
60160
|
+
mua = clamp( mua, 0.0, 1.0 );
|
|
60161
|
+
mub = ( d1343 + d4321 * ( mua ) ) / d4343;
|
|
60162
|
+
mub = clamp( mub, 0.0, 1.0 );
|
|
60163
|
+
|
|
60164
|
+
return vec2( mua, mub );
|
|
60165
|
+
|
|
60166
|
+
}
|
|
60167
|
+
|
|
60168
|
+
void main() {
|
|
60169
|
+
|
|
60170
|
+
#include <clipping_planes_fragment>
|
|
60171
|
+
|
|
60172
|
+
#ifdef USE_DASH
|
|
60173
|
+
|
|
60174
|
+
if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
|
|
60175
|
+
|
|
60176
|
+
if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
|
|
60177
|
+
|
|
60178
|
+
#endif
|
|
60179
|
+
|
|
60180
|
+
float alpha = opacity;
|
|
60181
|
+
|
|
60182
|
+
#ifdef WORLD_UNITS
|
|
60183
|
+
|
|
60184
|
+
// Find the closest points on the view ray and the line segment
|
|
60185
|
+
vec3 rayEnd = normalize( worldPos.xyz ) * 1e5;
|
|
60186
|
+
vec3 lineDir = worldEnd - worldStart;
|
|
60187
|
+
vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd );
|
|
60188
|
+
|
|
60189
|
+
vec3 p1 = worldStart + lineDir * params.x;
|
|
60190
|
+
vec3 p2 = rayEnd * params.y;
|
|
60191
|
+
vec3 delta = p1 - p2;
|
|
60192
|
+
float len = length( delta );
|
|
60193
|
+
float norm = len / linewidth;
|
|
60194
|
+
|
|
60195
|
+
#ifndef USE_DASH
|
|
60196
|
+
|
|
60197
|
+
#ifdef USE_ALPHA_TO_COVERAGE
|
|
60198
|
+
|
|
60199
|
+
float dnorm = fwidth( norm );
|
|
60200
|
+
alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm );
|
|
60201
|
+
|
|
60202
|
+
#else
|
|
60203
|
+
|
|
60204
|
+
if ( norm > 0.5 ) {
|
|
60205
|
+
|
|
60206
|
+
discard;
|
|
60207
|
+
|
|
60208
|
+
}
|
|
60209
|
+
|
|
60210
|
+
#endif
|
|
60211
|
+
|
|
60212
|
+
#endif
|
|
60213
|
+
|
|
60214
|
+
#else
|
|
60215
|
+
|
|
60216
|
+
#ifdef USE_ALPHA_TO_COVERAGE
|
|
60217
|
+
|
|
60218
|
+
// artifacts appear on some hardware if a derivative is taken within a conditional
|
|
60219
|
+
float a = vUv.x;
|
|
60220
|
+
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
|
|
60221
|
+
float len2 = a * a + b * b;
|
|
60222
|
+
float dlen = fwidth( len2 );
|
|
60223
|
+
|
|
60224
|
+
if ( abs( vUv.y ) > 1.0 ) {
|
|
60225
|
+
|
|
60226
|
+
alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 );
|
|
60227
|
+
|
|
60228
|
+
}
|
|
60229
|
+
|
|
60230
|
+
#else
|
|
60231
|
+
|
|
60232
|
+
if ( abs( vUv.y ) > 1.0 ) {
|
|
60233
|
+
|
|
60234
|
+
float a = vUv.x;
|
|
60235
|
+
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
|
|
60236
|
+
float len2 = a * a + b * b;
|
|
60237
|
+
|
|
60238
|
+
if ( len2 > 1.0 ) discard;
|
|
60239
|
+
|
|
60240
|
+
}
|
|
60241
|
+
|
|
60242
|
+
#endif
|
|
60243
|
+
|
|
60244
|
+
#endif
|
|
60245
|
+
|
|
60246
|
+
vec4 diffuseColor = vec4( diffuse, alpha );
|
|
60247
|
+
|
|
60248
|
+
#include <logdepthbuf_fragment>
|
|
60249
|
+
#include <color_fragment>
|
|
60250
|
+
|
|
60251
|
+
gl_FragColor = vec4( diffuseColor.rgb, alpha );
|
|
60252
|
+
|
|
60253
|
+
#include <tonemapping_fragment>
|
|
60254
|
+
#include <colorspace_fragment>
|
|
60255
|
+
#include <fog_fragment>
|
|
60256
|
+
#include <premultiplied_alpha_fragment>
|
|
60257
|
+
|
|
60258
|
+
}
|
|
60259
|
+
`
|
|
60260
|
+
};
|
|
60261
|
+
|
|
60262
|
+
class LineMaterial extends ShaderMaterial {
|
|
60263
|
+
|
|
60264
|
+
|
|
60265
|
+
static get type() {
|
|
60266
|
+
|
|
60267
|
+
return 'LineMaterial';
|
|
60268
|
+
|
|
60269
|
+
}
|
|
60270
|
+
|
|
60271
|
+
constructor( parameters ) {
|
|
60272
|
+
|
|
60273
|
+
super( {
|
|
60274
|
+
|
|
60275
|
+
uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ),
|
|
60276
|
+
|
|
60277
|
+
vertexShader: ShaderLib[ 'line' ].vertexShader,
|
|
60278
|
+
fragmentShader: ShaderLib[ 'line' ].fragmentShader,
|
|
60279
|
+
|
|
60280
|
+
clipping: true // required for clipping support
|
|
60281
|
+
|
|
60282
|
+
} );
|
|
60283
|
+
|
|
60284
|
+
this.isLineMaterial = true;
|
|
60285
|
+
|
|
60286
|
+
this.setValues( parameters );
|
|
60287
|
+
|
|
60288
|
+
}
|
|
60289
|
+
|
|
60290
|
+
get color() {
|
|
60291
|
+
|
|
60292
|
+
return this.uniforms.diffuse.value;
|
|
60293
|
+
|
|
60294
|
+
}
|
|
60295
|
+
|
|
60296
|
+
set color( value ) {
|
|
60297
|
+
|
|
60298
|
+
this.uniforms.diffuse.value = value;
|
|
60299
|
+
|
|
60300
|
+
}
|
|
60301
|
+
|
|
60302
|
+
get worldUnits() {
|
|
60303
|
+
|
|
60304
|
+
return 'WORLD_UNITS' in this.defines;
|
|
60305
|
+
|
|
60306
|
+
}
|
|
60307
|
+
|
|
60308
|
+
set worldUnits( value ) {
|
|
60309
|
+
|
|
60310
|
+
if ( value === true ) {
|
|
60311
|
+
|
|
60312
|
+
this.defines.WORLD_UNITS = '';
|
|
60313
|
+
|
|
60314
|
+
} else {
|
|
60315
|
+
|
|
60316
|
+
delete this.defines.WORLD_UNITS;
|
|
60317
|
+
|
|
60318
|
+
}
|
|
60319
|
+
|
|
60320
|
+
}
|
|
60321
|
+
|
|
60322
|
+
get linewidth() {
|
|
60323
|
+
|
|
60324
|
+
return this.uniforms.linewidth.value;
|
|
60325
|
+
|
|
60326
|
+
}
|
|
60327
|
+
|
|
60328
|
+
set linewidth( value ) {
|
|
60329
|
+
|
|
60330
|
+
if ( ! this.uniforms.linewidth ) return;
|
|
60331
|
+
this.uniforms.linewidth.value = value;
|
|
60332
|
+
|
|
60333
|
+
}
|
|
60334
|
+
|
|
60335
|
+
get dashed() {
|
|
60336
|
+
|
|
60337
|
+
return 'USE_DASH' in this.defines;
|
|
60338
|
+
|
|
60339
|
+
}
|
|
60340
|
+
|
|
60341
|
+
set dashed( value ) {
|
|
60342
|
+
|
|
60343
|
+
if ( ( value === true ) !== this.dashed ) {
|
|
60344
|
+
|
|
60345
|
+
this.needsUpdate = true;
|
|
60346
|
+
|
|
60347
|
+
}
|
|
60348
|
+
|
|
60349
|
+
if ( value === true ) {
|
|
60350
|
+
|
|
60351
|
+
this.defines.USE_DASH = '';
|
|
60352
|
+
|
|
60353
|
+
} else {
|
|
60354
|
+
|
|
60355
|
+
delete this.defines.USE_DASH;
|
|
60356
|
+
|
|
60357
|
+
}
|
|
60358
|
+
|
|
60359
|
+
}
|
|
60360
|
+
|
|
60361
|
+
get dashScale() {
|
|
60362
|
+
|
|
60363
|
+
return this.uniforms.dashScale.value;
|
|
60364
|
+
|
|
60365
|
+
}
|
|
60366
|
+
|
|
60367
|
+
set dashScale( value ) {
|
|
60368
|
+
|
|
60369
|
+
this.uniforms.dashScale.value = value;
|
|
60370
|
+
|
|
60371
|
+
}
|
|
60372
|
+
|
|
60373
|
+
get dashSize() {
|
|
60374
|
+
|
|
60375
|
+
return this.uniforms.dashSize.value;
|
|
60376
|
+
|
|
60377
|
+
}
|
|
60378
|
+
|
|
60379
|
+
set dashSize( value ) {
|
|
60380
|
+
|
|
60381
|
+
this.uniforms.dashSize.value = value;
|
|
60382
|
+
|
|
60383
|
+
}
|
|
60384
|
+
|
|
60385
|
+
get dashOffset() {
|
|
60386
|
+
|
|
60387
|
+
return this.uniforms.dashOffset.value;
|
|
60388
|
+
|
|
60389
|
+
}
|
|
60390
|
+
|
|
60391
|
+
set dashOffset( value ) {
|
|
60392
|
+
|
|
60393
|
+
this.uniforms.dashOffset.value = value;
|
|
60394
|
+
|
|
60395
|
+
}
|
|
60396
|
+
|
|
60397
|
+
get gapSize() {
|
|
60398
|
+
|
|
60399
|
+
return this.uniforms.gapSize.value;
|
|
60400
|
+
|
|
60401
|
+
}
|
|
60402
|
+
|
|
60403
|
+
set gapSize( value ) {
|
|
60404
|
+
|
|
60405
|
+
this.uniforms.gapSize.value = value;
|
|
60406
|
+
|
|
60407
|
+
}
|
|
60408
|
+
|
|
60409
|
+
get opacity() {
|
|
60410
|
+
|
|
60411
|
+
return this.uniforms.opacity.value;
|
|
60412
|
+
|
|
60413
|
+
}
|
|
60414
|
+
|
|
60415
|
+
set opacity( value ) {
|
|
60416
|
+
|
|
60417
|
+
if ( ! this.uniforms ) return;
|
|
60418
|
+
this.uniforms.opacity.value = value;
|
|
60419
|
+
|
|
60420
|
+
}
|
|
60421
|
+
|
|
60422
|
+
get resolution() {
|
|
60423
|
+
|
|
60424
|
+
return this.uniforms.resolution.value;
|
|
60425
|
+
|
|
60426
|
+
}
|
|
60427
|
+
|
|
60428
|
+
set resolution( value ) {
|
|
60429
|
+
|
|
60430
|
+
this.uniforms.resolution.value.copy( value );
|
|
60431
|
+
|
|
60432
|
+
}
|
|
60433
|
+
|
|
60434
|
+
get alphaToCoverage() {
|
|
60435
|
+
|
|
60436
|
+
return 'USE_ALPHA_TO_COVERAGE' in this.defines;
|
|
60437
|
+
|
|
60438
|
+
}
|
|
60439
|
+
|
|
60440
|
+
set alphaToCoverage( value ) {
|
|
60441
|
+
|
|
60442
|
+
if ( ! this.defines ) return;
|
|
60443
|
+
|
|
60444
|
+
if ( ( value === true ) !== this.alphaToCoverage ) {
|
|
60445
|
+
|
|
60446
|
+
this.needsUpdate = true;
|
|
60447
|
+
|
|
60448
|
+
}
|
|
60449
|
+
|
|
60450
|
+
if ( value === true ) {
|
|
60451
|
+
|
|
60452
|
+
this.defines.USE_ALPHA_TO_COVERAGE = '';
|
|
60453
|
+
|
|
60454
|
+
} else {
|
|
60455
|
+
|
|
60456
|
+
delete this.defines.USE_ALPHA_TO_COVERAGE;
|
|
60457
|
+
|
|
60458
|
+
}
|
|
60459
|
+
|
|
60460
|
+
}
|
|
60461
|
+
|
|
60462
|
+
}
|
|
60463
|
+
|
|
60464
|
+
const _viewport = new Vector4();
|
|
60465
|
+
|
|
60466
|
+
const _start = new Vector3();
|
|
60467
|
+
const _end = new Vector3();
|
|
60468
|
+
|
|
60469
|
+
const _start4 = new Vector4();
|
|
60470
|
+
const _end4 = new Vector4();
|
|
60471
|
+
|
|
60472
|
+
const _ssOrigin = new Vector4();
|
|
60473
|
+
const _ssOrigin3 = new Vector3();
|
|
60474
|
+
const _mvMatrix = new Matrix4();
|
|
60475
|
+
const _line = new Line3();
|
|
60476
|
+
const _closestPoint = new Vector3();
|
|
60477
|
+
|
|
60478
|
+
const _box = new Box3();
|
|
60479
|
+
const _sphere = new Sphere();
|
|
60480
|
+
const _clipToWorldVector = new Vector4();
|
|
60481
|
+
|
|
60482
|
+
let _ray, _lineWidth;
|
|
60483
|
+
|
|
60484
|
+
// Returns the margin required to expand by in world space given the distance from the camera,
|
|
60485
|
+
// line width, resolution, and camera projection
|
|
60486
|
+
function getWorldSpaceHalfWidth( camera, distance, resolution ) {
|
|
60487
|
+
|
|
60488
|
+
// transform into clip space, adjust the x and y values by the pixel width offset, then
|
|
60489
|
+
// transform back into world space to get world offset. Note clip space is [-1, 1] so full
|
|
60490
|
+
// width does not need to be halved.
|
|
60491
|
+
_clipToWorldVector.set( 0, 0, - distance, 1.0 ).applyMatrix4( camera.projectionMatrix );
|
|
60492
|
+
_clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w );
|
|
60493
|
+
_clipToWorldVector.x = _lineWidth / resolution.width;
|
|
60494
|
+
_clipToWorldVector.y = _lineWidth / resolution.height;
|
|
60495
|
+
_clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse );
|
|
60496
|
+
_clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w );
|
|
60497
|
+
|
|
60498
|
+
return Math.abs( Math.max( _clipToWorldVector.x, _clipToWorldVector.y ) );
|
|
60499
|
+
|
|
60500
|
+
}
|
|
60501
|
+
|
|
60502
|
+
function raycastWorldUnits( lineSegments, intersects ) {
|
|
60503
|
+
|
|
60504
|
+
const matrixWorld = lineSegments.matrixWorld;
|
|
60505
|
+
const geometry = lineSegments.geometry;
|
|
60506
|
+
const instanceStart = geometry.attributes.instanceStart;
|
|
60507
|
+
const instanceEnd = geometry.attributes.instanceEnd;
|
|
60508
|
+
const segmentCount = Math.min( geometry.instanceCount, instanceStart.count );
|
|
60509
|
+
|
|
60510
|
+
for ( let i = 0, l = segmentCount; i < l; i ++ ) {
|
|
60511
|
+
|
|
60512
|
+
_line.start.fromBufferAttribute( instanceStart, i );
|
|
60513
|
+
_line.end.fromBufferAttribute( instanceEnd, i );
|
|
60514
|
+
|
|
60515
|
+
_line.applyMatrix4( matrixWorld );
|
|
60516
|
+
|
|
60517
|
+
const pointOnLine = new Vector3();
|
|
60518
|
+
const point = new Vector3();
|
|
60519
|
+
|
|
60520
|
+
_ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine );
|
|
60521
|
+
const isInside = point.distanceTo( pointOnLine ) < _lineWidth * 0.5;
|
|
60522
|
+
|
|
60523
|
+
if ( isInside ) {
|
|
60524
|
+
|
|
60525
|
+
intersects.push( {
|
|
60526
|
+
point,
|
|
60527
|
+
pointOnLine,
|
|
60528
|
+
distance: _ray.origin.distanceTo( point ),
|
|
60529
|
+
object: lineSegments,
|
|
60530
|
+
face: null,
|
|
60531
|
+
faceIndex: i,
|
|
60532
|
+
uv: null,
|
|
60533
|
+
uv1: null,
|
|
60534
|
+
} );
|
|
60535
|
+
|
|
60536
|
+
}
|
|
60537
|
+
|
|
60538
|
+
}
|
|
60539
|
+
|
|
60540
|
+
}
|
|
60541
|
+
|
|
60542
|
+
function raycastScreenSpace( lineSegments, camera, intersects ) {
|
|
60543
|
+
|
|
60544
|
+
const projectionMatrix = camera.projectionMatrix;
|
|
60545
|
+
const material = lineSegments.material;
|
|
60546
|
+
const resolution = material.resolution;
|
|
60547
|
+
const matrixWorld = lineSegments.matrixWorld;
|
|
60548
|
+
|
|
60549
|
+
const geometry = lineSegments.geometry;
|
|
60550
|
+
const instanceStart = geometry.attributes.instanceStart;
|
|
60551
|
+
const instanceEnd = geometry.attributes.instanceEnd;
|
|
60552
|
+
const segmentCount = Math.min( geometry.instanceCount, instanceStart.count );
|
|
60553
|
+
|
|
60554
|
+
const near = - camera.near;
|
|
60555
|
+
|
|
60556
|
+
//
|
|
60557
|
+
|
|
60558
|
+
// pick a point 1 unit out along the ray to avoid the ray origin
|
|
60559
|
+
// sitting at the camera origin which will cause "w" to be 0 when
|
|
60560
|
+
// applying the projection matrix.
|
|
60561
|
+
_ray.at( 1, _ssOrigin );
|
|
60562
|
+
|
|
60563
|
+
// ndc space [ - 1.0, 1.0 ]
|
|
60564
|
+
_ssOrigin.w = 1;
|
|
60565
|
+
_ssOrigin.applyMatrix4( camera.matrixWorldInverse );
|
|
60566
|
+
_ssOrigin.applyMatrix4( projectionMatrix );
|
|
60567
|
+
_ssOrigin.multiplyScalar( 1 / _ssOrigin.w );
|
|
60568
|
+
|
|
60569
|
+
// screen space
|
|
60570
|
+
_ssOrigin.x *= resolution.x / 2;
|
|
60571
|
+
_ssOrigin.y *= resolution.y / 2;
|
|
60572
|
+
_ssOrigin.z = 0;
|
|
60573
|
+
|
|
60574
|
+
_ssOrigin3.copy( _ssOrigin );
|
|
60575
|
+
|
|
60576
|
+
_mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
|
|
60577
|
+
|
|
60578
|
+
for ( let i = 0, l = segmentCount; i < l; i ++ ) {
|
|
60579
|
+
|
|
60580
|
+
_start4.fromBufferAttribute( instanceStart, i );
|
|
60581
|
+
_end4.fromBufferAttribute( instanceEnd, i );
|
|
60582
|
+
|
|
60583
|
+
_start4.w = 1;
|
|
60584
|
+
_end4.w = 1;
|
|
60585
|
+
|
|
60586
|
+
// camera space
|
|
60587
|
+
_start4.applyMatrix4( _mvMatrix );
|
|
60588
|
+
_end4.applyMatrix4( _mvMatrix );
|
|
60589
|
+
|
|
60590
|
+
// skip the segment if it's entirely behind the camera
|
|
60591
|
+
const isBehindCameraNear = _start4.z > near && _end4.z > near;
|
|
60592
|
+
if ( isBehindCameraNear ) {
|
|
60593
|
+
|
|
60594
|
+
continue;
|
|
60595
|
+
|
|
60596
|
+
}
|
|
60597
|
+
|
|
60598
|
+
// trim the segment if it extends behind camera near
|
|
60599
|
+
if ( _start4.z > near ) {
|
|
60600
|
+
|
|
60601
|
+
const deltaDist = _start4.z - _end4.z;
|
|
60602
|
+
const t = ( _start4.z - near ) / deltaDist;
|
|
60603
|
+
_start4.lerp( _end4, t );
|
|
60604
|
+
|
|
60605
|
+
} else if ( _end4.z > near ) {
|
|
60606
|
+
|
|
60607
|
+
const deltaDist = _end4.z - _start4.z;
|
|
60608
|
+
const t = ( _end4.z - near ) / deltaDist;
|
|
60609
|
+
_end4.lerp( _start4, t );
|
|
60610
|
+
|
|
60611
|
+
}
|
|
60612
|
+
|
|
60613
|
+
// clip space
|
|
60614
|
+
_start4.applyMatrix4( projectionMatrix );
|
|
60615
|
+
_end4.applyMatrix4( projectionMatrix );
|
|
60616
|
+
|
|
60617
|
+
// ndc space [ - 1.0, 1.0 ]
|
|
60618
|
+
_start4.multiplyScalar( 1 / _start4.w );
|
|
60619
|
+
_end4.multiplyScalar( 1 / _end4.w );
|
|
60620
|
+
|
|
60621
|
+
// screen space
|
|
60622
|
+
_start4.x *= resolution.x / 2;
|
|
60623
|
+
_start4.y *= resolution.y / 2;
|
|
60624
|
+
|
|
60625
|
+
_end4.x *= resolution.x / 2;
|
|
60626
|
+
_end4.y *= resolution.y / 2;
|
|
60627
|
+
|
|
60628
|
+
// create 2d segment
|
|
60629
|
+
_line.start.copy( _start4 );
|
|
60630
|
+
_line.start.z = 0;
|
|
60631
|
+
|
|
60632
|
+
_line.end.copy( _end4 );
|
|
60633
|
+
_line.end.z = 0;
|
|
60634
|
+
|
|
60635
|
+
// get closest point on ray to segment
|
|
60636
|
+
const param = _line.closestPointToPointParameter( _ssOrigin3, true );
|
|
60637
|
+
_line.at( param, _closestPoint );
|
|
60638
|
+
|
|
60639
|
+
// check if the intersection point is within clip space
|
|
60640
|
+
const zPos = MathUtils.lerp( _start4.z, _end4.z, param );
|
|
60641
|
+
const isInClipSpace = zPos >= - 1 && zPos <= 1;
|
|
60642
|
+
|
|
60643
|
+
const isInside = _ssOrigin3.distanceTo( _closestPoint ) < _lineWidth * 0.5;
|
|
60644
|
+
|
|
60645
|
+
if ( isInClipSpace && isInside ) {
|
|
60646
|
+
|
|
60647
|
+
_line.start.fromBufferAttribute( instanceStart, i );
|
|
60648
|
+
_line.end.fromBufferAttribute( instanceEnd, i );
|
|
60649
|
+
|
|
60650
|
+
_line.start.applyMatrix4( matrixWorld );
|
|
60651
|
+
_line.end.applyMatrix4( matrixWorld );
|
|
60652
|
+
|
|
60653
|
+
const pointOnLine = new Vector3();
|
|
60654
|
+
const point = new Vector3();
|
|
60655
|
+
|
|
60656
|
+
_ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine );
|
|
60657
|
+
|
|
60658
|
+
intersects.push( {
|
|
60659
|
+
point: point,
|
|
60660
|
+
pointOnLine: pointOnLine,
|
|
60661
|
+
distance: _ray.origin.distanceTo( point ),
|
|
60662
|
+
object: lineSegments,
|
|
60663
|
+
face: null,
|
|
60664
|
+
faceIndex: i,
|
|
60665
|
+
uv: null,
|
|
60666
|
+
uv1: null,
|
|
60667
|
+
} );
|
|
60668
|
+
|
|
60669
|
+
}
|
|
60670
|
+
|
|
60671
|
+
}
|
|
60672
|
+
|
|
60673
|
+
}
|
|
60674
|
+
|
|
60675
|
+
class LineSegments2 extends Mesh {
|
|
60676
|
+
|
|
60677
|
+
constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) {
|
|
60678
|
+
|
|
60679
|
+
super( geometry, material );
|
|
60680
|
+
|
|
60681
|
+
this.isLineSegments2 = true;
|
|
60682
|
+
|
|
60683
|
+
this.type = 'LineSegments2';
|
|
60684
|
+
|
|
60685
|
+
}
|
|
60686
|
+
|
|
60687
|
+
// for backwards-compatibility, but could be a method of LineSegmentsGeometry...
|
|
60688
|
+
|
|
60689
|
+
computeLineDistances() {
|
|
60690
|
+
|
|
60691
|
+
const geometry = this.geometry;
|
|
60692
|
+
|
|
60693
|
+
const instanceStart = geometry.attributes.instanceStart;
|
|
60694
|
+
const instanceEnd = geometry.attributes.instanceEnd;
|
|
60695
|
+
const lineDistances = new Float32Array( 2 * instanceStart.count );
|
|
60696
|
+
|
|
60697
|
+
for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) {
|
|
60698
|
+
|
|
60699
|
+
_start.fromBufferAttribute( instanceStart, i );
|
|
60700
|
+
_end.fromBufferAttribute( instanceEnd, i );
|
|
60701
|
+
|
|
60702
|
+
lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
|
|
60703
|
+
lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end );
|
|
60704
|
+
|
|
60705
|
+
}
|
|
60706
|
+
|
|
60707
|
+
const instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
|
|
60708
|
+
|
|
60709
|
+
geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
|
|
60710
|
+
geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
|
|
60711
|
+
|
|
60712
|
+
return this;
|
|
60713
|
+
|
|
60714
|
+
}
|
|
60715
|
+
|
|
60716
|
+
raycast( raycaster, intersects ) {
|
|
60717
|
+
|
|
60718
|
+
const worldUnits = this.material.worldUnits;
|
|
60719
|
+
const camera = raycaster.camera;
|
|
60720
|
+
|
|
60721
|
+
if ( camera === null && ! worldUnits ) {
|
|
60722
|
+
|
|
60723
|
+
console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2 while worldUnits is set to false.' );
|
|
60724
|
+
|
|
60725
|
+
}
|
|
60726
|
+
|
|
60727
|
+
const threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0;
|
|
60728
|
+
|
|
60729
|
+
_ray = raycaster.ray;
|
|
60730
|
+
|
|
60731
|
+
const matrixWorld = this.matrixWorld;
|
|
60732
|
+
const geometry = this.geometry;
|
|
60733
|
+
const material = this.material;
|
|
60734
|
+
|
|
60735
|
+
_lineWidth = material.linewidth + threshold;
|
|
60736
|
+
|
|
60737
|
+
// check if we intersect the sphere bounds
|
|
60738
|
+
if ( geometry.boundingSphere === null ) {
|
|
60739
|
+
|
|
60740
|
+
geometry.computeBoundingSphere();
|
|
60741
|
+
|
|
60742
|
+
}
|
|
60743
|
+
|
|
60744
|
+
_sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld );
|
|
60745
|
+
|
|
60746
|
+
// increase the sphere bounds by the worst case line screen space width
|
|
60747
|
+
let sphereMargin;
|
|
60748
|
+
if ( worldUnits ) {
|
|
60749
|
+
|
|
60750
|
+
sphereMargin = _lineWidth * 0.5;
|
|
60751
|
+
|
|
60752
|
+
} else {
|
|
60753
|
+
|
|
60754
|
+
const distanceToSphere = Math.max( camera.near, _sphere.distanceToPoint( _ray.origin ) );
|
|
60755
|
+
sphereMargin = getWorldSpaceHalfWidth( camera, distanceToSphere, material.resolution );
|
|
60756
|
+
|
|
60757
|
+
}
|
|
60758
|
+
|
|
60759
|
+
_sphere.radius += sphereMargin;
|
|
60760
|
+
|
|
60761
|
+
if ( _ray.intersectsSphere( _sphere ) === false ) {
|
|
60762
|
+
|
|
60763
|
+
return;
|
|
60764
|
+
|
|
60765
|
+
}
|
|
60766
|
+
|
|
60767
|
+
// check if we intersect the box bounds
|
|
60768
|
+
if ( geometry.boundingBox === null ) {
|
|
60769
|
+
|
|
60770
|
+
geometry.computeBoundingBox();
|
|
60771
|
+
|
|
60772
|
+
}
|
|
60773
|
+
|
|
60774
|
+
_box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld );
|
|
60775
|
+
|
|
60776
|
+
// increase the box bounds by the worst case line width
|
|
60777
|
+
let boxMargin;
|
|
60778
|
+
if ( worldUnits ) {
|
|
60779
|
+
|
|
60780
|
+
boxMargin = _lineWidth * 0.5;
|
|
60781
|
+
|
|
60782
|
+
} else {
|
|
60783
|
+
|
|
60784
|
+
const distanceToBox = Math.max( camera.near, _box.distanceToPoint( _ray.origin ) );
|
|
60785
|
+
boxMargin = getWorldSpaceHalfWidth( camera, distanceToBox, material.resolution );
|
|
60786
|
+
|
|
60787
|
+
}
|
|
60788
|
+
|
|
60789
|
+
_box.expandByScalar( boxMargin );
|
|
60790
|
+
|
|
60791
|
+
if ( _ray.intersectsBox( _box ) === false ) {
|
|
60792
|
+
|
|
60793
|
+
return;
|
|
60794
|
+
|
|
60795
|
+
}
|
|
60796
|
+
|
|
60797
|
+
if ( worldUnits ) {
|
|
60798
|
+
|
|
60799
|
+
raycastWorldUnits( this, intersects );
|
|
60800
|
+
|
|
60801
|
+
} else {
|
|
60802
|
+
|
|
60803
|
+
raycastScreenSpace( this, camera, intersects );
|
|
60804
|
+
|
|
60805
|
+
}
|
|
60806
|
+
|
|
60807
|
+
}
|
|
60808
|
+
|
|
60809
|
+
onBeforeRender( renderer ) {
|
|
60810
|
+
|
|
60811
|
+
const uniforms = this.material.uniforms;
|
|
60812
|
+
|
|
60813
|
+
if ( uniforms && uniforms.resolution ) {
|
|
60814
|
+
|
|
60815
|
+
renderer.getViewport( _viewport );
|
|
60816
|
+
this.material.uniforms.resolution.value.set( _viewport.z, _viewport.w );
|
|
60817
|
+
|
|
60818
|
+
}
|
|
60819
|
+
|
|
60820
|
+
}
|
|
60821
|
+
|
|
60822
|
+
}
|
|
60823
|
+
|
|
60824
|
+
class LineGeometry extends LineSegmentsGeometry {
|
|
60825
|
+
|
|
60826
|
+
constructor() {
|
|
60827
|
+
|
|
60828
|
+
super();
|
|
60829
|
+
|
|
60830
|
+
this.isLineGeometry = true;
|
|
60831
|
+
|
|
60832
|
+
this.type = 'LineGeometry';
|
|
60833
|
+
|
|
60834
|
+
}
|
|
60835
|
+
|
|
60836
|
+
setPositions( array ) {
|
|
60837
|
+
|
|
60838
|
+
// converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format
|
|
60839
|
+
|
|
60840
|
+
const length = array.length - 3;
|
|
60841
|
+
const points = new Float32Array( 2 * length );
|
|
60842
|
+
|
|
60843
|
+
for ( let i = 0; i < length; i += 3 ) {
|
|
60844
|
+
|
|
60845
|
+
points[ 2 * i ] = array[ i ];
|
|
60846
|
+
points[ 2 * i + 1 ] = array[ i + 1 ];
|
|
60847
|
+
points[ 2 * i + 2 ] = array[ i + 2 ];
|
|
60848
|
+
|
|
60849
|
+
points[ 2 * i + 3 ] = array[ i + 3 ];
|
|
60850
|
+
points[ 2 * i + 4 ] = array[ i + 4 ];
|
|
60851
|
+
points[ 2 * i + 5 ] = array[ i + 5 ];
|
|
60852
|
+
|
|
60853
|
+
}
|
|
60854
|
+
|
|
60855
|
+
super.setPositions( points );
|
|
60856
|
+
|
|
60857
|
+
return this;
|
|
60858
|
+
|
|
60859
|
+
}
|
|
60860
|
+
|
|
60861
|
+
setColors( array ) {
|
|
60862
|
+
|
|
60863
|
+
// converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format
|
|
60864
|
+
|
|
60865
|
+
const length = array.length - 3;
|
|
60866
|
+
const colors = new Float32Array( 2 * length );
|
|
60867
|
+
|
|
60868
|
+
for ( let i = 0; i < length; i += 3 ) {
|
|
60869
|
+
|
|
60870
|
+
colors[ 2 * i ] = array[ i ];
|
|
60871
|
+
colors[ 2 * i + 1 ] = array[ i + 1 ];
|
|
60872
|
+
colors[ 2 * i + 2 ] = array[ i + 2 ];
|
|
60873
|
+
|
|
60874
|
+
colors[ 2 * i + 3 ] = array[ i + 3 ];
|
|
60875
|
+
colors[ 2 * i + 4 ] = array[ i + 4 ];
|
|
60876
|
+
colors[ 2 * i + 5 ] = array[ i + 5 ];
|
|
60877
|
+
|
|
60878
|
+
}
|
|
60879
|
+
|
|
60880
|
+
super.setColors( colors );
|
|
60881
|
+
|
|
60882
|
+
return this;
|
|
60883
|
+
|
|
60884
|
+
}
|
|
60885
|
+
|
|
60886
|
+
fromLine( line ) {
|
|
60887
|
+
|
|
60888
|
+
const geometry = line.geometry;
|
|
60889
|
+
|
|
60890
|
+
this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
|
|
60891
|
+
|
|
60892
|
+
// set colors, maybe
|
|
60893
|
+
|
|
60894
|
+
return this;
|
|
60895
|
+
|
|
60896
|
+
}
|
|
60897
|
+
|
|
60898
|
+
}
|
|
60899
|
+
|
|
60900
|
+
class Line2 extends LineSegments2 {
|
|
60901
|
+
|
|
60902
|
+
constructor( geometry = new LineGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) {
|
|
60903
|
+
|
|
60904
|
+
super( geometry, material );
|
|
60905
|
+
|
|
60906
|
+
this.isLine2 = true;
|
|
60907
|
+
|
|
60908
|
+
this.type = 'Line2';
|
|
60909
|
+
|
|
60910
|
+
}
|
|
60911
|
+
|
|
60912
|
+
}
|
|
60913
|
+
|
|
60914
|
+
/**
|
|
60915
|
+
* 把标注顶点(local)序列展平成 Line2 需要的 world [x,y,z, ...]。
|
|
60916
|
+
* 每个点 local→world,再沿 world 法线外移 epsilon,避免与模型表面 z-fighting。
|
|
60917
|
+
* closed 时把首点追加到末尾形成环。
|
|
60918
|
+
*/
|
|
60919
|
+
function flatten(verts, closed, epsilon, mesh) {
|
|
60920
|
+
const pts = [];
|
|
60921
|
+
const push = (v) => {
|
|
60922
|
+
const { p, n } = localVertexToWorld(v, mesh);
|
|
60923
|
+
pts.push(p.x + n.x * epsilon, p.y + n.y * epsilon, p.z + n.z * epsilon);
|
|
60924
|
+
};
|
|
60925
|
+
verts.forEach(push);
|
|
60926
|
+
if (closed && verts.length > 1)
|
|
60927
|
+
push(verts[0]);
|
|
60928
|
+
return pts;
|
|
60929
|
+
}
|
|
60930
|
+
function makeContourLine(verts, color, closed, container, epsilon, mesh) {
|
|
60931
|
+
const geo = new LineGeometry();
|
|
60932
|
+
const pos = flatten(verts, closed, epsilon, mesh);
|
|
60933
|
+
// Line2 至少需要 2 个点;不足时给一个退化占位,后续 update 会补上。
|
|
60934
|
+
geo.setPositions(pos.length >= 6 ? pos : [0, 0, 0, 0, 0, 0]);
|
|
60935
|
+
const mat = new LineMaterial({
|
|
60936
|
+
color: new Color$1(color).getHex(),
|
|
60937
|
+
linewidth: 3, // 像素宽(需 resolution 配合)
|
|
60938
|
+
});
|
|
60939
|
+
mat.resolution.set(container.clientWidth, container.clientHeight);
|
|
60940
|
+
const line = new Line2(geo, mat);
|
|
60941
|
+
line.computeLineDistances();
|
|
60942
|
+
line.renderOrder = 998;
|
|
60943
|
+
// 关键:Line2 的包围球停留在初始点,拖拽生长后真实几何会被错误剔除。
|
|
60944
|
+
// 关闭视锥剔除,保证线始终渲染。
|
|
60945
|
+
line.frustumCulled = false;
|
|
60946
|
+
return line;
|
|
60947
|
+
}
|
|
60948
|
+
function updateContourLine(line, verts, closed, epsilon, mesh) {
|
|
60949
|
+
const pos = flatten(verts, closed, epsilon, mesh);
|
|
60950
|
+
if (pos.length < 6)
|
|
60951
|
+
return; // Line2 需要 >=2 点
|
|
60952
|
+
// 关键:Line2 在原 geometry 上 setPositions 扩容后只渲染首段(已知坑)。
|
|
60953
|
+
// 改为每次重建一个新 LineGeometry 并替换,保证实例数正确、全段渲染。
|
|
60954
|
+
const geo = new LineGeometry();
|
|
60955
|
+
geo.setPositions(pos);
|
|
60956
|
+
const old = line.geometry;
|
|
60957
|
+
line.geometry = geo;
|
|
60958
|
+
old.dispose();
|
|
60959
|
+
line.computeLineDistances();
|
|
60960
|
+
}
|
|
60961
|
+
/** 改变已有 contour 线的颜色。 */
|
|
60962
|
+
function setContourColor(line, color) {
|
|
60963
|
+
line.material.color.set(color);
|
|
60964
|
+
}
|
|
60965
|
+
|
|
60966
|
+
/**
|
|
60967
|
+
* 从 BufferGeometry 的索引构建顶点邻接图,用于模式 B(测地线)的最短路径。
|
|
60968
|
+
* 顶点坐标按 local 存;传入的查询点需先转为 local(mesh.worldToLocal)。
|
|
60969
|
+
*
|
|
60970
|
+
* 说明:几何须为已索引(indexed)。modelLoader 用 mergeVertices 焊接重复顶点,
|
|
60971
|
+
* 保证同一表面位置共享顶点,邻接图才连通。
|
|
60972
|
+
*
|
|
60973
|
+
* 性能:O(V²) Dijkstra + O(V) 最近顶点查找。对几万顶点单次点击可接受;
|
|
60974
|
+
* 若过大可后续换二叉堆 + 空间网格加速(留作升级)。
|
|
60975
|
+
*/
|
|
60976
|
+
class MeshGraph {
|
|
60977
|
+
constructor(geometry) {
|
|
60978
|
+
this.adj = [];
|
|
60979
|
+
const pos = geometry.getAttribute("position");
|
|
60980
|
+
this.positions = pos.array;
|
|
60981
|
+
const nrm = geometry.getAttribute("normal");
|
|
60982
|
+
this.normals = nrm ? nrm.array : null;
|
|
60983
|
+
this.vertexCount = pos.count;
|
|
60984
|
+
for (let i = 0; i < this.vertexCount; i++)
|
|
60985
|
+
this.adj.push(new Set());
|
|
60986
|
+
const addEdge = (a, b) => {
|
|
60987
|
+
this.adj[a].add(b);
|
|
60988
|
+
this.adj[b].add(a);
|
|
60989
|
+
};
|
|
60990
|
+
const index = geometry.getIndex();
|
|
60991
|
+
if (index) {
|
|
60992
|
+
for (let i = 0; i < index.count; i += 3) {
|
|
60993
|
+
const a = index.getX(i);
|
|
60994
|
+
const b = index.getX(i + 1);
|
|
60995
|
+
const c = index.getX(i + 2);
|
|
60996
|
+
addEdge(a, b);
|
|
60997
|
+
addEdge(b, c);
|
|
60998
|
+
addEdge(c, a);
|
|
60999
|
+
}
|
|
61000
|
+
}
|
|
61001
|
+
else {
|
|
61002
|
+
// 非索引兜底(连通性差,仅避免崩溃)。
|
|
61003
|
+
for (let i = 0; i + 2 < this.vertexCount; i += 3) {
|
|
61004
|
+
addEdge(i, i + 1);
|
|
61005
|
+
addEdge(i + 1, i + 2);
|
|
61006
|
+
addEdge(i + 2, i);
|
|
61007
|
+
}
|
|
61008
|
+
}
|
|
61009
|
+
}
|
|
61010
|
+
px(i) {
|
|
61011
|
+
return this.positions[i * 3];
|
|
61012
|
+
}
|
|
61013
|
+
py(i) {
|
|
61014
|
+
return this.positions[i * 3 + 1];
|
|
61015
|
+
}
|
|
61016
|
+
pz(i) {
|
|
61017
|
+
return this.positions[i * 3 + 2];
|
|
61018
|
+
}
|
|
61019
|
+
/** 最近顶点(传入 local 坐标)。 */
|
|
61020
|
+
nearestVertex(localPoint) {
|
|
61021
|
+
let best = -1;
|
|
61022
|
+
let bestD = Infinity;
|
|
61023
|
+
for (let i = 0; i < this.vertexCount; i++) {
|
|
61024
|
+
const dx = this.px(i) - localPoint.x;
|
|
61025
|
+
const dy = this.py(i) - localPoint.y;
|
|
61026
|
+
const dz = this.pz(i) - localPoint.z;
|
|
61027
|
+
const d = dx * dx + dy * dy + dz * dz;
|
|
61028
|
+
if (d < bestD) {
|
|
61029
|
+
bestD = d;
|
|
61030
|
+
best = i;
|
|
61031
|
+
}
|
|
61032
|
+
}
|
|
61033
|
+
return best;
|
|
61034
|
+
}
|
|
61035
|
+
/**
|
|
61036
|
+
* 两顶点间最短路径(含端点的顶点索引序列)。二叉堆 Dijkstra,O(E log V)。
|
|
61037
|
+
* 不连通时返回 [start,end] 兜底。
|
|
61038
|
+
*/
|
|
61039
|
+
shortestPath(startV, endV) {
|
|
61040
|
+
if (startV === endV)
|
|
61041
|
+
return [startV];
|
|
61042
|
+
const dist = new Float64Array(this.vertexCount).fill(Infinity);
|
|
61043
|
+
const prev = new Int32Array(this.vertexCount).fill(-1);
|
|
61044
|
+
const done = new Uint8Array(this.vertexCount);
|
|
61045
|
+
dist[startV] = 0;
|
|
61046
|
+
const heap = new MinHeap();
|
|
61047
|
+
heap.push(startV, 0);
|
|
61048
|
+
while (heap.size > 0) {
|
|
61049
|
+
const u = heap.pop();
|
|
61050
|
+
if (done[u])
|
|
61051
|
+
continue;
|
|
61052
|
+
done[u] = 1;
|
|
61053
|
+
if (u === endV)
|
|
61054
|
+
break;
|
|
61055
|
+
const ud = dist[u];
|
|
61056
|
+
const ux = this.px(u);
|
|
61057
|
+
const uy = this.py(u);
|
|
61058
|
+
const uz = this.pz(u);
|
|
61059
|
+
for (const w of this.adj[u]) {
|
|
61060
|
+
if (done[w])
|
|
61061
|
+
continue;
|
|
61062
|
+
const dx = this.px(w) - ux;
|
|
61063
|
+
const dy = this.py(w) - uy;
|
|
61064
|
+
const dz = this.pz(w) - uz;
|
|
61065
|
+
const nd = ud + Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
61066
|
+
if (nd < dist[w]) {
|
|
61067
|
+
dist[w] = nd;
|
|
61068
|
+
prev[w] = u;
|
|
61069
|
+
heap.push(w, nd);
|
|
61070
|
+
}
|
|
61071
|
+
}
|
|
61072
|
+
}
|
|
61073
|
+
if (prev[endV] === -1 && startV !== endV)
|
|
61074
|
+
return [startV, endV];
|
|
61075
|
+
const path = [];
|
|
61076
|
+
for (let c = endV; c !== -1; c = prev[c]) {
|
|
61077
|
+
path.push(c);
|
|
61078
|
+
if (c === startV)
|
|
61079
|
+
break;
|
|
61080
|
+
}
|
|
61081
|
+
return path.reverse();
|
|
61082
|
+
}
|
|
61083
|
+
vertexWorld(i, matrixWorld) {
|
|
61084
|
+
return new Vector3(this.px(i), this.py(i), this.pz(i)).applyMatrix4(matrixWorld);
|
|
61085
|
+
}
|
|
61086
|
+
vertexNormalWorld(i, mesh) {
|
|
61087
|
+
const n = new Vector3(0, 0, 1);
|
|
61088
|
+
if (this.normals) {
|
|
61089
|
+
n.set(this.normals[i * 3], this.normals[i * 3 + 1], this.normals[i * 3 + 2]);
|
|
61090
|
+
}
|
|
61091
|
+
const nm = new Matrix3().getNormalMatrix(mesh.matrixWorld);
|
|
61092
|
+
return n.applyMatrix3(nm).normalize();
|
|
61093
|
+
}
|
|
61094
|
+
/** 顶点的 local 坐标 + local 法线(图几何即 local 空间)。 */
|
|
61095
|
+
vertexLocal(i) {
|
|
61096
|
+
return {
|
|
61097
|
+
x: this.px(i),
|
|
61098
|
+
y: this.py(i),
|
|
61099
|
+
z: this.pz(i),
|
|
61100
|
+
nx: this.normals ? this.normals[i * 3] : 0,
|
|
61101
|
+
ny: this.normals ? this.normals[i * 3 + 1] : 0,
|
|
61102
|
+
nz: this.normals ? this.normals[i * 3 + 2] : 1,
|
|
61103
|
+
faceIndex: -1,
|
|
61104
|
+
};
|
|
61105
|
+
}
|
|
61106
|
+
}
|
|
61107
|
+
/** 极简二叉最小堆(按 priority 排序,存顶点索引)。用于 Dijkstra。 */
|
|
61108
|
+
class MinHeap {
|
|
61109
|
+
constructor() {
|
|
61110
|
+
this.ids = [];
|
|
61111
|
+
this.prio = [];
|
|
61112
|
+
}
|
|
61113
|
+
get size() {
|
|
61114
|
+
return this.ids.length;
|
|
61115
|
+
}
|
|
61116
|
+
push(id, priority) {
|
|
61117
|
+
this.ids.push(id);
|
|
61118
|
+
this.prio.push(priority);
|
|
61119
|
+
let i = this.ids.length - 1;
|
|
61120
|
+
while (i > 0) {
|
|
61121
|
+
const parent = (i - 1) >> 1;
|
|
61122
|
+
if (this.prio[parent] <= this.prio[i])
|
|
61123
|
+
break;
|
|
61124
|
+
this.swap(i, parent);
|
|
61125
|
+
i = parent;
|
|
61126
|
+
}
|
|
61127
|
+
}
|
|
61128
|
+
pop() {
|
|
61129
|
+
const topId = this.ids[0];
|
|
61130
|
+
const lastId = this.ids.pop();
|
|
61131
|
+
const lastPrio = this.prio.pop();
|
|
61132
|
+
if (this.ids.length > 0) {
|
|
61133
|
+
this.ids[0] = lastId;
|
|
61134
|
+
this.prio[0] = lastPrio;
|
|
61135
|
+
let i = 0;
|
|
61136
|
+
const n = this.ids.length;
|
|
61137
|
+
for (;;) {
|
|
61138
|
+
const l = 2 * i + 1;
|
|
61139
|
+
const r = 2 * i + 2;
|
|
61140
|
+
let smallest = i;
|
|
61141
|
+
if (l < n && this.prio[l] < this.prio[smallest])
|
|
61142
|
+
smallest = l;
|
|
61143
|
+
if (r < n && this.prio[r] < this.prio[smallest])
|
|
61144
|
+
smallest = r;
|
|
61145
|
+
if (smallest === i)
|
|
61146
|
+
break;
|
|
61147
|
+
this.swap(i, smallest);
|
|
61148
|
+
i = smallest;
|
|
61149
|
+
}
|
|
61150
|
+
}
|
|
61151
|
+
return topId;
|
|
61152
|
+
}
|
|
61153
|
+
swap(a, b) {
|
|
61154
|
+
const ti = this.ids[a];
|
|
61155
|
+
this.ids[a] = this.ids[b];
|
|
61156
|
+
this.ids[b] = ti;
|
|
61157
|
+
const tp = this.prio[a];
|
|
61158
|
+
this.prio[a] = this.prio[b];
|
|
61159
|
+
this.prio[b] = tp;
|
|
61160
|
+
}
|
|
61161
|
+
}
|
|
61162
|
+
|
|
61163
|
+
/**
|
|
61164
|
+
* 模式 B(测地线)状态机:逐次点击锚点,相邻锚点之间用 MeshGraph 求最短路径,
|
|
61165
|
+
* 拼成一条贴合表面的折线。Enter 结束并可闭合(首尾再求一段)。
|
|
61166
|
+
*/
|
|
61167
|
+
class GeodesicContour {
|
|
61168
|
+
constructor(graph, mesh) {
|
|
61169
|
+
this.graph = graph;
|
|
61170
|
+
this.mesh = mesh;
|
|
61171
|
+
this.anchors = []; // 顶点索引
|
|
61172
|
+
this.segments = []; // 相邻锚点间路径(含端点)
|
|
61173
|
+
}
|
|
61174
|
+
/** 传入 local 命中点,snap 到最近顶点并对上一锚点求路径。 */
|
|
61175
|
+
addAnchor(localHitPoint) {
|
|
61176
|
+
const v = this.graph.nearestVertex(localHitPoint);
|
|
61177
|
+
if (this.anchors.length > 0) {
|
|
61178
|
+
const prev = this.anchors[this.anchors.length - 1];
|
|
61179
|
+
this.segments.push(this.graph.shortestPath(prev, v));
|
|
61180
|
+
}
|
|
61181
|
+
this.anchors.push(v);
|
|
61182
|
+
}
|
|
61183
|
+
get anchorCount() {
|
|
61184
|
+
return this.anchors.length;
|
|
61185
|
+
}
|
|
61186
|
+
/** 把所有路径顶点(world)+ 法线 拼成折线;closed 时补一段首尾路径。 */
|
|
61187
|
+
buildVertices(closed) {
|
|
61188
|
+
const segs = this.segments.slice();
|
|
61189
|
+
if (closed && this.anchors.length > 2) {
|
|
61190
|
+
segs.push(this.graph.shortestPath(this.anchors[this.anchors.length - 1], this.anchors[0]));
|
|
61191
|
+
}
|
|
61192
|
+
const idxPath = [];
|
|
61193
|
+
segs.forEach((s, si) => {
|
|
61194
|
+
const start = si === 0 ? 0 : 1; // 去重相邻段共享端点
|
|
61195
|
+
for (let k = start; k < s.length; k++)
|
|
61196
|
+
idxPath.push(s[k]);
|
|
61197
|
+
});
|
|
61198
|
+
if (idxPath.length === 0 && this.anchors.length === 1) {
|
|
61199
|
+
idxPath.push(this.anchors[0]);
|
|
61200
|
+
}
|
|
61201
|
+
// 图几何即 local 空间,直接产出 local 顶点(渲染时再派生 world)。
|
|
61202
|
+
return idxPath.map((i) => this.graph.vertexLocal(i));
|
|
61203
|
+
}
|
|
61204
|
+
}
|
|
61205
|
+
|
|
61206
|
+
/**
|
|
61207
|
+
* 标注数据模型 + 撤销栈 + 导出。纯数据,无 three / DOM 依赖。
|
|
61208
|
+
* 撤销只记录 add / remove 两类原子操作(满足"撤销最近添加 / 恢复删除")。
|
|
61209
|
+
* 通过 subscribe 通知 UI 与渲染层(后者据 list() 做场景对账)。
|
|
61210
|
+
*/
|
|
61211
|
+
class AnnotationStore {
|
|
61212
|
+
constructor() {
|
|
61213
|
+
this.items = [];
|
|
61214
|
+
this.undoStack = [];
|
|
61215
|
+
this.subs = new Set();
|
|
61216
|
+
}
|
|
61217
|
+
notify() {
|
|
61218
|
+
this.subs.forEach((f) => f());
|
|
61219
|
+
}
|
|
61220
|
+
subscribe(cb) {
|
|
61221
|
+
this.subs.add(cb);
|
|
61222
|
+
return () => this.subs.delete(cb);
|
|
61223
|
+
}
|
|
61224
|
+
add(a) {
|
|
61225
|
+
this.items.push(a);
|
|
61226
|
+
this.undoStack.push({ kind: "add", ann: a });
|
|
61227
|
+
this.notify();
|
|
61228
|
+
}
|
|
61229
|
+
remove(id) {
|
|
61230
|
+
const i = this.items.findIndex((x) => x.id === id);
|
|
61231
|
+
if (i < 0)
|
|
61232
|
+
return undefined;
|
|
61233
|
+
const [ann] = this.items.splice(i, 1);
|
|
61234
|
+
this.undoStack.push({ kind: "remove", ann });
|
|
61235
|
+
this.notify();
|
|
61236
|
+
return ann;
|
|
61237
|
+
}
|
|
61238
|
+
undo() {
|
|
61239
|
+
const op = this.undoStack.pop();
|
|
61240
|
+
if (!op)
|
|
61241
|
+
return;
|
|
61242
|
+
if (op.kind === "add") {
|
|
61243
|
+
const i = this.items.findIndex((x) => x.id === op.ann.id);
|
|
61244
|
+
if (i >= 0)
|
|
61245
|
+
this.items.splice(i, 1);
|
|
61246
|
+
}
|
|
61247
|
+
else {
|
|
61248
|
+
this.items.push(op.ann);
|
|
61249
|
+
}
|
|
61250
|
+
this.notify();
|
|
61251
|
+
}
|
|
61252
|
+
/** 清空,返回被清掉的项(供渲染层 dispose three 对象)。 */
|
|
61253
|
+
clear() {
|
|
61254
|
+
const old = this.items;
|
|
61255
|
+
this.items = [];
|
|
61256
|
+
this.undoStack = [];
|
|
61257
|
+
this.notify();
|
|
61258
|
+
return old;
|
|
61259
|
+
}
|
|
61260
|
+
list() {
|
|
61261
|
+
return this.items;
|
|
61262
|
+
}
|
|
61263
|
+
get(id) {
|
|
61264
|
+
return this.items.find((x) => x.id === id);
|
|
61265
|
+
}
|
|
61266
|
+
setLabel(id, label) {
|
|
61267
|
+
const a = this.get(id);
|
|
61268
|
+
if (a) {
|
|
61269
|
+
a.label = label;
|
|
61270
|
+
this.notify();
|
|
61271
|
+
}
|
|
61272
|
+
}
|
|
61273
|
+
setColor(id, color) {
|
|
61274
|
+
const a = this.get(id);
|
|
61275
|
+
if (a) {
|
|
61276
|
+
a.color = color;
|
|
61277
|
+
this.notify();
|
|
61278
|
+
}
|
|
61279
|
+
}
|
|
61280
|
+
toJSON(modelName, mesh, opts = {}) {
|
|
61281
|
+
var _a;
|
|
61282
|
+
const space = (_a = opts.space) !== null && _a !== void 0 ? _a : "local";
|
|
61283
|
+
const toPt = (v) => {
|
|
61284
|
+
let x = v.x, y = v.y, z = v.z;
|
|
61285
|
+
if (space === "world") {
|
|
61286
|
+
const p = new Vector3(v.x, v.y, v.z).applyMatrix4(mesh.matrixWorld);
|
|
61287
|
+
x = p.x;
|
|
61288
|
+
y = p.y;
|
|
61289
|
+
z = p.z;
|
|
61290
|
+
}
|
|
61291
|
+
return opts.includeNormals ? [x, y, z, v.nx, v.ny, v.nz] : [x, y, z];
|
|
61292
|
+
};
|
|
61293
|
+
return {
|
|
61294
|
+
model: modelName,
|
|
61295
|
+
exportedAt: new Date().toISOString(),
|
|
61296
|
+
space,
|
|
61297
|
+
annotations: this.items.map((a) => ({
|
|
61298
|
+
id: a.id,
|
|
61299
|
+
type: a.type,
|
|
61300
|
+
mode: a.mode,
|
|
61301
|
+
label: a.label,
|
|
61302
|
+
color: a.color,
|
|
61303
|
+
closed: a.closed,
|
|
61304
|
+
points: a.vertices.map(toPt),
|
|
61305
|
+
})),
|
|
61306
|
+
};
|
|
61307
|
+
}
|
|
61308
|
+
}
|
|
61309
|
+
|
|
61310
|
+
const LINE_W = 3;
|
|
61311
|
+
const LINE_W_SEL = 6;
|
|
61312
|
+
/**
|
|
61313
|
+
* 表面标注主控制器(Phase 4):navigate / freehand / geodesic / point 四模式,
|
|
61314
|
+
* Enter 闭合,数据交给 AnnotationStore 管理(多条带颜色标签、撤销/删除/清空、导出)。
|
|
61315
|
+
* 渲染层据 store.list() 做场景对账(reconcile),撤销/删除自动加/移 three 对象。
|
|
61316
|
+
*/
|
|
61317
|
+
class SurfaceAnnotator {
|
|
61318
|
+
constructor(opts) {
|
|
61319
|
+
var _a, _b;
|
|
61320
|
+
this.mode = "navigate";
|
|
61321
|
+
this.spaceHeld = false;
|
|
61322
|
+
this.pointerDown = false;
|
|
61323
|
+
this.store = new AnnotationStore();
|
|
61324
|
+
this.managed = new Set();
|
|
61325
|
+
this.seq = 0;
|
|
61326
|
+
this.selectedId = null;
|
|
61327
|
+
/** 窗口尺寸变化时更新所有 fat line 的像素分辨率,否则线宽会失真。 */
|
|
61328
|
+
this.onResize = () => {
|
|
61329
|
+
const w = this.o.container.clientWidth;
|
|
61330
|
+
const h = this.o.container.clientHeight;
|
|
61331
|
+
for (const a of this.store.list()) {
|
|
61332
|
+
if (a.type === "contour" && a.object3D) {
|
|
61333
|
+
const mat = a.object3D.material;
|
|
61334
|
+
mat.resolution.set(w, h);
|
|
61335
|
+
}
|
|
61336
|
+
}
|
|
61337
|
+
if (this.activeLine)
|
|
61338
|
+
(this.activeLine.material.resolution.set(w, h));
|
|
61339
|
+
if (this.activeGeoLine)
|
|
61340
|
+
(this.activeGeoLine.material.resolution.set(w, h));
|
|
61341
|
+
};
|
|
61342
|
+
this.onPointerDown = (e) => {
|
|
61343
|
+
if (this.spaceHeld)
|
|
61344
|
+
return;
|
|
61345
|
+
if (e.button !== 0)
|
|
61346
|
+
return; // 仅左键
|
|
61347
|
+
if (!this.insideContainer(e))
|
|
61348
|
+
return;
|
|
61349
|
+
this.pointerDown = true;
|
|
61350
|
+
if (this.mode === "point") {
|
|
61351
|
+
const h = this.hit(e);
|
|
61352
|
+
if (!h)
|
|
61353
|
+
return;
|
|
61354
|
+
const v = worldHitToLocalVertex(h, this.o.mesh);
|
|
61355
|
+
const marker = makePointMarker(v, this.o.mesh, this.pointColorVal, this.markerRadius);
|
|
61356
|
+
this.o.scene.add(marker);
|
|
61357
|
+
const ann = {
|
|
61358
|
+
id: this.nextId(),
|
|
61359
|
+
type: "points",
|
|
61360
|
+
mode: null,
|
|
61361
|
+
label: `Point ${this.seq}`,
|
|
61362
|
+
color: this.pointColorVal,
|
|
61363
|
+
closed: false,
|
|
61364
|
+
vertices: [v],
|
|
61365
|
+
object3D: marker,
|
|
61366
|
+
};
|
|
61367
|
+
this.store.add(ann);
|
|
61368
|
+
return;
|
|
61369
|
+
}
|
|
61370
|
+
if (this.mode === "freehand") {
|
|
61371
|
+
this.activeStroke = new StrokeContour(this.minGap, this.maxJump, this.o.mesh);
|
|
61372
|
+
this.activeStroke.begin();
|
|
61373
|
+
const h = this.hit(e);
|
|
61374
|
+
if (h)
|
|
61375
|
+
this.activeStroke.addSample(h);
|
|
61376
|
+
this.activeLine = makeContourLine(this.activeStroke.vertices, this.freehandColor, false, this.o.container, this.epsilon, this.o.mesh);
|
|
61377
|
+
this.o.scene.add(this.activeLine);
|
|
61378
|
+
return;
|
|
61379
|
+
}
|
|
61380
|
+
if (this.mode === "geodesic") {
|
|
61381
|
+
const h = this.hit(e);
|
|
61382
|
+
if (!h)
|
|
61383
|
+
return;
|
|
61384
|
+
if (!this.activeGeo) {
|
|
61385
|
+
this.activeGeo = new GeodesicContour(this.graph, this.o.mesh);
|
|
61386
|
+
}
|
|
61387
|
+
const local = this.o.mesh.worldToLocal(h.point.clone());
|
|
61388
|
+
this.activeGeo.addAnchor(local);
|
|
61389
|
+
const verts = this.activeGeo.buildVertices(false);
|
|
61390
|
+
if (!this.activeGeoLine) {
|
|
61391
|
+
this.activeGeoLine = makeContourLine(verts, this.geodesicColor, false, this.o.container, this.epsilon, this.o.mesh);
|
|
61392
|
+
this.o.scene.add(this.activeGeoLine);
|
|
61393
|
+
}
|
|
61394
|
+
else {
|
|
61395
|
+
updateContourLine(this.activeGeoLine, verts, false, this.epsilon, this.o.mesh);
|
|
61396
|
+
}
|
|
61397
|
+
}
|
|
61398
|
+
};
|
|
61399
|
+
this.onPointerMove = (e) => {
|
|
61400
|
+
if (this.spaceHeld)
|
|
61401
|
+
return;
|
|
61402
|
+
if (this.mode === "freehand" &&
|
|
61403
|
+
this.pointerDown &&
|
|
61404
|
+
this.activeStroke &&
|
|
61405
|
+
this.activeLine) {
|
|
61406
|
+
const h = this.hit(e);
|
|
61407
|
+
if (!h)
|
|
61408
|
+
return;
|
|
61409
|
+
this.activeStroke.addSample(h);
|
|
61410
|
+
updateContourLine(this.activeLine, this.activeStroke.vertices, false, this.epsilon, this.o.mesh);
|
|
61411
|
+
}
|
|
61412
|
+
};
|
|
61413
|
+
this.onPointerUp = () => {
|
|
61414
|
+
if (!this.pointerDown)
|
|
61415
|
+
return;
|
|
61416
|
+
this.pointerDown = false;
|
|
61417
|
+
if (this.activeStroke && this.activeLine) {
|
|
61418
|
+
const verts = this.activeStroke.end();
|
|
61419
|
+
if (verts.length >= 2) {
|
|
61420
|
+
const ann = {
|
|
61421
|
+
id: this.nextId(),
|
|
61422
|
+
type: "contour",
|
|
61423
|
+
mode: "freehand",
|
|
61424
|
+
label: `Contour ${this.seq}`,
|
|
61425
|
+
color: this.freehandColor,
|
|
61426
|
+
closed: false,
|
|
61427
|
+
vertices: verts,
|
|
61428
|
+
object3D: this.activeLine,
|
|
61429
|
+
};
|
|
61430
|
+
this.store.add(ann);
|
|
61431
|
+
this.lastFreehand = ann;
|
|
61432
|
+
}
|
|
61433
|
+
else {
|
|
61434
|
+
this.o.scene.remove(this.activeLine);
|
|
61435
|
+
}
|
|
61436
|
+
this.activeStroke = undefined;
|
|
61437
|
+
this.activeLine = undefined;
|
|
61438
|
+
}
|
|
61439
|
+
};
|
|
61440
|
+
this.onKeyDown = (e) => {
|
|
61441
|
+
if (this.isTypingTarget(e))
|
|
61442
|
+
return;
|
|
61443
|
+
if (e.code === "Space") {
|
|
61444
|
+
this.spaceHeld = true;
|
|
61445
|
+
this.applyCameraGating();
|
|
61446
|
+
return;
|
|
61447
|
+
}
|
|
61448
|
+
if (e.key === "Escape") {
|
|
61449
|
+
this.setMode("navigate");
|
|
61450
|
+
return;
|
|
61451
|
+
}
|
|
61452
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === "z" || e.key === "Z")) {
|
|
61453
|
+
e.preventDefault();
|
|
61454
|
+
this.undo();
|
|
61455
|
+
return;
|
|
61456
|
+
}
|
|
61457
|
+
if (e.key === "Delete" || e.key === "Backspace") {
|
|
61458
|
+
if (this.selectedId)
|
|
61459
|
+
this.deleteAnnotation(this.selectedId);
|
|
61460
|
+
return;
|
|
61461
|
+
}
|
|
61462
|
+
if (e.key === "Enter") {
|
|
61463
|
+
// 测地线进行中 → 结束并闭合;否则闭合最近一条自由手绘线。
|
|
61464
|
+
if (this.activeGeo)
|
|
61465
|
+
this.finishGeodesic();
|
|
61466
|
+
else
|
|
61467
|
+
this.closeLastContour();
|
|
61468
|
+
return;
|
|
61469
|
+
}
|
|
61470
|
+
if (e.key === "1")
|
|
61471
|
+
this.setMode("navigate");
|
|
61472
|
+
if (e.key === "2")
|
|
61473
|
+
this.setMode("freehand");
|
|
61474
|
+
if (e.key === "3")
|
|
61475
|
+
this.setMode("geodesic");
|
|
61476
|
+
if (e.key === "4")
|
|
61477
|
+
this.setMode("point");
|
|
61478
|
+
};
|
|
61479
|
+
this.onKeyUp = (e) => {
|
|
61480
|
+
if (e.code === "Space") {
|
|
61481
|
+
this.spaceHeld = false;
|
|
61482
|
+
this.applyCameraGating();
|
|
61483
|
+
}
|
|
61484
|
+
};
|
|
61485
|
+
this.o = opts;
|
|
61486
|
+
const meshGeo = opts.mesh.geometry;
|
|
61487
|
+
if (!meshGeo.getAttribute("normal"))
|
|
61488
|
+
meshGeo.computeVertexNormals(); // 渲染需要法线
|
|
61489
|
+
meshGeo.computeBoundingBox();
|
|
61490
|
+
const diag = (_a = opts.bboxDiagonal) !== null && _a !== void 0 ? _a : meshGeo.boundingBox.getSize(new Vector3()).length();
|
|
61491
|
+
this.markerRadius = (_b = opts.markerRadius) !== null && _b !== void 0 ? _b : diag * 0.006;
|
|
61492
|
+
this.epsilon = diag * 0.002;
|
|
61493
|
+
this.minGap = diag * 0.004;
|
|
61494
|
+
this.maxJump = diag * 0.05;
|
|
61495
|
+
// 测地线连通性:单独焊接一份"仅位置"的几何给 MeshGraph 用——
|
|
61496
|
+
// 不动被渲染的 mesh(保留其法线/UV/纹理)。仅位置可保证同表面位置的顶点
|
|
61497
|
+
// 被 mergeVertices 合并(否则逐面法线/UV 会阻止合并,图断裂 → 闭合穿模)。
|
|
61498
|
+
const posOnly = new BufferGeometry();
|
|
61499
|
+
posOnly.setAttribute("position", meshGeo.getAttribute("position").clone());
|
|
61500
|
+
const graphGeo = mergeVertices(posOnly);
|
|
61501
|
+
graphGeo.computeVertexNormals();
|
|
61502
|
+
this.graph = new MeshGraph(graphGeo);
|
|
61503
|
+
this.store.subscribe(() => this.reconcile());
|
|
61504
|
+
// 监听挂到 window 的捕获阶段(capture=true):在 copper 的 TrackballControls
|
|
61505
|
+
// 之前拿到每一个指针事件,避免它的 setPointerCapture / 事件路由导致拖拽中途丢 move。
|
|
61506
|
+
window.addEventListener("pointerdown", this.onPointerDown, true);
|
|
61507
|
+
window.addEventListener("pointermove", this.onPointerMove, true);
|
|
61508
|
+
window.addEventListener("pointerup", this.onPointerUp, true);
|
|
61509
|
+
window.addEventListener("keydown", this.onKeyDown);
|
|
61510
|
+
window.addEventListener("keyup", this.onKeyUp);
|
|
61511
|
+
window.addEventListener("resize", this.onResize);
|
|
61512
|
+
this.applyCameraGating();
|
|
61513
|
+
}
|
|
61514
|
+
get freehandColor() {
|
|
61515
|
+
var _a;
|
|
61516
|
+
return (_a = this.o.freehandColor) !== null && _a !== void 0 ? _a : "#e5006e";
|
|
61517
|
+
}
|
|
61518
|
+
get geodesicColor() {
|
|
61519
|
+
var _a;
|
|
61520
|
+
return (_a = this.o.geodesicColor) !== null && _a !== void 0 ? _a : "#ffa24e";
|
|
61521
|
+
}
|
|
61522
|
+
get pointColorVal() {
|
|
61523
|
+
var _a;
|
|
61524
|
+
return (_a = this.o.pointColor) !== null && _a !== void 0 ? _a : "#ffd166";
|
|
61525
|
+
}
|
|
61526
|
+
// ---- 对外 API(供 Vue 调用) ----
|
|
61527
|
+
getMode() {
|
|
61528
|
+
return this.mode;
|
|
61529
|
+
}
|
|
61530
|
+
setMode(m) {
|
|
61531
|
+
var _a, _b;
|
|
61532
|
+
this.mode = m;
|
|
61533
|
+
this.applyCameraGating();
|
|
61534
|
+
(_b = (_a = this.o).onModeChange) === null || _b === void 0 ? void 0 : _b.call(_a, m);
|
|
61535
|
+
}
|
|
61536
|
+
getStore() {
|
|
61537
|
+
return this.store;
|
|
61538
|
+
}
|
|
61539
|
+
/** 当前标注列表快照。 */
|
|
61540
|
+
getAnnotations() {
|
|
61541
|
+
return this.store.list();
|
|
61542
|
+
}
|
|
61543
|
+
undo() {
|
|
61544
|
+
this.store.undo();
|
|
61545
|
+
}
|
|
61546
|
+
clearAll() {
|
|
61547
|
+
const removed = this.store.clear();
|
|
61548
|
+
removed.forEach((a) => {
|
|
61549
|
+
if (a.object3D) {
|
|
61550
|
+
this.o.scene.remove(a.object3D);
|
|
61551
|
+
this.disposeObject(a.object3D);
|
|
61552
|
+
}
|
|
61553
|
+
});
|
|
61554
|
+
this.managed.clear();
|
|
61555
|
+
this.selectedId = null;
|
|
61556
|
+
}
|
|
61557
|
+
deleteAnnotation(id) {
|
|
61558
|
+
if (this.selectedId === id)
|
|
61559
|
+
this.selectedId = null;
|
|
61560
|
+
this.store.remove(id);
|
|
61561
|
+
}
|
|
61562
|
+
selectAnnotation(id) {
|
|
61563
|
+
this.selectedId = id;
|
|
61564
|
+
this.applySelection();
|
|
61565
|
+
}
|
|
61566
|
+
/** 颜色变更后重画对应 three 对象。 */
|
|
61567
|
+
refreshAnnotation(id) {
|
|
61568
|
+
const a = this.store.get(id);
|
|
61569
|
+
if (!a || !a.object3D)
|
|
61570
|
+
return;
|
|
61571
|
+
if (a.type === "contour") {
|
|
61572
|
+
setContourColor(a.object3D, a.color);
|
|
61573
|
+
}
|
|
61574
|
+
else {
|
|
61575
|
+
const m = a.object3D;
|
|
61576
|
+
m.material.color.set(a.color);
|
|
61577
|
+
}
|
|
61578
|
+
}
|
|
61579
|
+
exportJSON(modelName, opts) {
|
|
61580
|
+
return this.store.toJSON(modelName, this.o.mesh, opts);
|
|
61581
|
+
}
|
|
61582
|
+
// ---- 内部 ----
|
|
61583
|
+
applyCameraGating() {
|
|
61584
|
+
// 按住 Space 临时旋转优先;否则仅 navigate 模式开相机。
|
|
61585
|
+
this.o.controls.enabled = this.spaceHeld || this.mode === "navigate";
|
|
61586
|
+
}
|
|
61587
|
+
/** 据 store.list() 对账场景:补齐缺失对象、移除已删对象(不 dispose,留给撤销恢复)。 */
|
|
61588
|
+
reconcile() {
|
|
61589
|
+
var _a, _b;
|
|
61590
|
+
const present = new Set();
|
|
61591
|
+
for (const a of this.store.list())
|
|
61592
|
+
if (a.object3D)
|
|
61593
|
+
present.add(a.object3D);
|
|
61594
|
+
for (const o of this.managed) {
|
|
61595
|
+
if (!present.has(o))
|
|
61596
|
+
this.o.scene.remove(o);
|
|
61597
|
+
}
|
|
61598
|
+
for (const o of present) {
|
|
61599
|
+
if (!this.managed.has(o))
|
|
61600
|
+
this.o.scene.add(o);
|
|
61601
|
+
}
|
|
61602
|
+
this.managed = present;
|
|
61603
|
+
this.applySelection();
|
|
61604
|
+
(_b = (_a = this.o).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, this.store.list());
|
|
61605
|
+
}
|
|
61606
|
+
applySelection() {
|
|
61607
|
+
for (const a of this.store.list()) {
|
|
61608
|
+
if (!a.object3D)
|
|
61609
|
+
continue;
|
|
61610
|
+
const sel = a.id === this.selectedId;
|
|
61611
|
+
if (a.type === "contour") {
|
|
61612
|
+
const mat = a.object3D.material;
|
|
61613
|
+
mat.linewidth = sel ? LINE_W_SEL : LINE_W;
|
|
61614
|
+
}
|
|
61615
|
+
else {
|
|
61616
|
+
a.object3D.scale.setScalar(sel ? 1.6 : 1);
|
|
61617
|
+
}
|
|
61618
|
+
}
|
|
61619
|
+
}
|
|
61620
|
+
disposeObject(o) {
|
|
61621
|
+
var _a, _b, _c;
|
|
61622
|
+
const any = o;
|
|
61623
|
+
(_b = (_a = any.geometry) === null || _a === void 0 ? void 0 : _a.dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
61624
|
+
const mat = any.material;
|
|
61625
|
+
if (Array.isArray(mat))
|
|
61626
|
+
mat.forEach((m) => m.dispose());
|
|
61627
|
+
else
|
|
61628
|
+
(_c = mat === null || mat === void 0 ? void 0 : mat.dispose) === null || _c === void 0 ? void 0 : _c.call(mat);
|
|
61629
|
+
}
|
|
61630
|
+
nextId() {
|
|
61631
|
+
return "a" + ++this.seq;
|
|
61632
|
+
}
|
|
61633
|
+
hit(e) {
|
|
61634
|
+
return raycastSurface(this.o.camera, this.o.container, this.o.mesh, e.clientX, e.clientY);
|
|
61635
|
+
}
|
|
61636
|
+
/** 事件目标是否落在标注容器内(排除面板/页面其它区域的点击)。 */
|
|
61637
|
+
insideContainer(e) {
|
|
61638
|
+
const t = e.target;
|
|
61639
|
+
return !!t && this.o.container.contains(t);
|
|
61640
|
+
}
|
|
61641
|
+
closeLastContour() {
|
|
61642
|
+
const last = this.lastFreehand;
|
|
61643
|
+
if (!last || last.closed || last.vertices.length < 3 || !last.object3D)
|
|
61644
|
+
return;
|
|
61645
|
+
// 用测地线沿表面把末点连回首点,避免直线弦从模型内部"抄近路"被遮挡。
|
|
61646
|
+
const tail = last.vertices[last.vertices.length - 1];
|
|
61647
|
+
const head = last.vertices[0];
|
|
61648
|
+
const closing = this.surfacePathBetween(tail, head);
|
|
61649
|
+
last.vertices = last.vertices.concat(closing);
|
|
61650
|
+
last.closed = true;
|
|
61651
|
+
updateContourLine(last.object3D, last.vertices, true, this.epsilon, this.o.mesh);
|
|
61652
|
+
}
|
|
61653
|
+
/** 沿网格表面求 a→b 的测地路径(local 顶点),去掉与 a 重合的首点。a/b 已是 local。 */
|
|
61654
|
+
surfacePathBetween(a, b) {
|
|
61655
|
+
const va = this.graph.nearestVertex(new Vector3(a.x, a.y, a.z));
|
|
61656
|
+
const vb = this.graph.nearestVertex(new Vector3(b.x, b.y, b.z));
|
|
61657
|
+
const path = this.graph.shortestPath(va, vb);
|
|
61658
|
+
return path.slice(1).map((i) => this.graph.vertexLocal(i));
|
|
61659
|
+
}
|
|
61660
|
+
/** 结束当前测地线:闭合成环并落定为一条 contour。 */
|
|
61661
|
+
finishGeodesic() {
|
|
61662
|
+
if (!this.activeGeo || !this.activeGeoLine)
|
|
61663
|
+
return;
|
|
61664
|
+
const closed = this.activeGeo.anchorCount > 2;
|
|
61665
|
+
const verts = this.activeGeo.buildVertices(closed);
|
|
61666
|
+
if (verts.length >= 2) {
|
|
61667
|
+
updateContourLine(this.activeGeoLine, verts, closed, this.epsilon, this.o.mesh);
|
|
61668
|
+
const ann = {
|
|
61669
|
+
id: this.nextId(),
|
|
61670
|
+
type: "contour",
|
|
61671
|
+
mode: "geodesic",
|
|
61672
|
+
label: `Contour ${this.seq}`,
|
|
61673
|
+
color: this.geodesicColor,
|
|
61674
|
+
closed,
|
|
61675
|
+
vertices: verts,
|
|
61676
|
+
object3D: this.activeGeoLine,
|
|
61677
|
+
};
|
|
61678
|
+
this.store.add(ann);
|
|
61679
|
+
}
|
|
61680
|
+
else {
|
|
61681
|
+
this.o.scene.remove(this.activeGeoLine);
|
|
61682
|
+
}
|
|
61683
|
+
this.activeGeo = undefined;
|
|
61684
|
+
this.activeGeoLine = undefined;
|
|
61685
|
+
}
|
|
61686
|
+
isTypingTarget(e) {
|
|
61687
|
+
const t = e.target;
|
|
61688
|
+
if (!t)
|
|
61689
|
+
return false;
|
|
61690
|
+
const tag = t.tagName;
|
|
61691
|
+
return tag === "INPUT" || tag === "TEXTAREA" || t.isContentEditable;
|
|
61692
|
+
}
|
|
61693
|
+
dispose() {
|
|
61694
|
+
window.removeEventListener("pointerdown", this.onPointerDown, true);
|
|
61695
|
+
window.removeEventListener("pointermove", this.onPointerMove, true);
|
|
61696
|
+
window.removeEventListener("pointerup", this.onPointerUp, true);
|
|
61697
|
+
window.removeEventListener("keydown", this.onKeyDown);
|
|
61698
|
+
window.removeEventListener("keyup", this.onKeyUp);
|
|
61699
|
+
window.removeEventListener("resize", this.onResize);
|
|
61700
|
+
}
|
|
61701
|
+
}
|
|
61702
|
+
|
|
58892
61703
|
class copperScene extends baseScene {
|
|
58893
61704
|
// rayster pick
|
|
58894
61705
|
constructor(container, renderer, opt) {
|
|
@@ -58898,6 +61709,7 @@ void main() {
|
|
|
58898
61709
|
this.mixer = null;
|
|
58899
61710
|
this.playRate = 1.0;
|
|
58900
61711
|
this.modelReady = false;
|
|
61712
|
+
this.surfaceAnnotators = [];
|
|
58901
61713
|
if ((opt === null || opt === void 0 ? void 0 : opt.controls) === "trackball") {
|
|
58902
61714
|
this.controls = new TrackballControls(this.camera, this.renderer.domElement);
|
|
58903
61715
|
}
|
|
@@ -58973,53 +61785,45 @@ void main() {
|
|
|
58973
61785
|
console.log("An error happened: ", error);
|
|
58974
61786
|
});
|
|
58975
61787
|
}
|
|
58976
|
-
// loadOBJ(url: string, callback?: (mesh: THREE.Group) => void) {
|
|
58977
|
-
// objLoader.load(
|
|
58978
|
-
// url,
|
|
58979
|
-
// (obj) => {
|
|
58980
|
-
// obj.traverse((child) => {
|
|
58981
|
-
// if ((child as THREE.Mesh).isMesh) {
|
|
58982
|
-
// // (child as THREE.Mesh).material = new THREE.MeshStandardMaterial({
|
|
58983
|
-
// // side: THREE.DoubleSide,
|
|
58984
|
-
// // color: 0xffffff,
|
|
58985
|
-
// // });
|
|
58986
|
-
// // ((child as THREE.Mesh).material as THREE.MeshPhongMaterial).color =
|
|
58987
|
-
// // new THREE.Color(0xffffff);
|
|
58988
|
-
// }
|
|
58989
|
-
// });
|
|
58990
|
-
// const box = new THREE.Box3().setFromObject(obj);
|
|
58991
|
-
// const size = box.getSize(new THREE.Vector3()).length();
|
|
58992
|
-
// const center = box.getCenter(new THREE.Vector3());
|
|
58993
|
-
// this.controls.maxDistance = size * 10;
|
|
58994
|
-
// obj.position.x += obj.position.x - center.x;
|
|
58995
|
-
// obj.position.y += obj.position.y - center.y;
|
|
58996
|
-
// obj.position.z += obj.position.z - center.z;
|
|
58997
|
-
// if (!this.cameraPositionFlag) {
|
|
58998
|
-
// this.camera.position.copy(center);
|
|
58999
|
-
// this.camera.position.x += size / 2.0;
|
|
59000
|
-
// this.camera.position.y += size / 5.0;
|
|
59001
|
-
// this.camera.position.z += size / 2.0;
|
|
59002
|
-
// this.camera.lookAt(center);
|
|
59003
|
-
// this.viewPoint = this.setViewPoint(
|
|
59004
|
-
// this.camera as THREE.PerspectiveCamera,
|
|
59005
|
-
// [center.x, center.y, center.z]
|
|
59006
|
-
// );
|
|
59007
|
-
// }
|
|
59008
|
-
// this.scene.add(obj);
|
|
59009
|
-
// !!callback && callback(obj);
|
|
59010
|
-
// }, // called when loading is in progresses
|
|
59011
|
-
// (xhr: any) => {
|
|
59012
|
-
// console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
|
|
59013
|
-
// },
|
|
59014
|
-
// // called when loading has errors
|
|
59015
|
-
// (error: any) => {
|
|
59016
|
-
// console.log("An error happened");
|
|
59017
|
-
// }
|
|
59018
|
-
// );
|
|
59019
|
-
// }
|
|
59020
61788
|
loadVtk(url) {
|
|
59021
61789
|
copperVtkLoader(url, this.scene, this.content);
|
|
59022
61790
|
}
|
|
61791
|
+
/**
|
|
61792
|
+
* 在给定模型表面创建标注器(画 contour / 放点 / 导出坐标)。
|
|
61793
|
+
* target 可为单个 Mesh,或 Group/Object3D(自动选顶点数最多的 mesh)。
|
|
61794
|
+
* 复用本 scene 的 camera / container / controls;几何非索引时内部自动焊接索引化。
|
|
61795
|
+
*/
|
|
61796
|
+
createSurfaceAnnotator(target, opts) {
|
|
61797
|
+
const mesh = this.pickAnnotatableMesh(target);
|
|
61798
|
+
if (!mesh)
|
|
61799
|
+
throw new Error("createSurfaceAnnotator: no mesh found in target");
|
|
61800
|
+
const annotator = new SurfaceAnnotator(Object.assign({ scene: this.scene, camera: this.camera, container: this.container, controls: this.controls, mesh }, (opts !== null && opts !== void 0 ? opts : {})));
|
|
61801
|
+
this.surfaceAnnotators.push(annotator);
|
|
61802
|
+
return annotator;
|
|
61803
|
+
}
|
|
61804
|
+
pickAnnotatableMesh(target) {
|
|
61805
|
+
if (target.isMesh)
|
|
61806
|
+
return target;
|
|
61807
|
+
let best = null;
|
|
61808
|
+
let bestCount = -1;
|
|
61809
|
+
target.traverse((c) => {
|
|
61810
|
+
var _a, _b;
|
|
61811
|
+
const m = c;
|
|
61812
|
+
if (m.isMesh) {
|
|
61813
|
+
const n = (_b = (_a = m.geometry.getAttribute("position")) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0;
|
|
61814
|
+
if (n > bestCount) {
|
|
61815
|
+
bestCount = n;
|
|
61816
|
+
best = m;
|
|
61817
|
+
}
|
|
61818
|
+
}
|
|
61819
|
+
});
|
|
61820
|
+
return best;
|
|
61821
|
+
}
|
|
61822
|
+
/** 释放本 scene 创建的所有标注器(移除事件监听与标注对象)。 */
|
|
61823
|
+
disposeSurfaceAnnotators() {
|
|
61824
|
+
this.surfaceAnnotators.forEach((a) => a.dispose());
|
|
61825
|
+
this.surfaceAnnotators = [];
|
|
61826
|
+
}
|
|
59023
61827
|
loadVtks(models) {
|
|
59024
61828
|
let count = 0;
|
|
59025
61829
|
let { vtkLoader } = copperMultipleVtk();
|
|
@@ -79874,7 +82678,7 @@ void main() {
|
|
|
79874
82678
|
}
|
|
79875
82679
|
|
|
79876
82680
|
// import * as kiwrious from "copper3d_plugin_heart_k";
|
|
79877
|
-
const REVISION = "v3.
|
|
82681
|
+
const REVISION = "v3.6.1-beta";
|
|
79878
82682
|
console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
|
|
79879
82683
|
|
|
79880
82684
|
exports.AI_CHANNEL_HEX_COLORS = AI_CHANNEL_HEX_COLORS;
|
|
@@ -79887,6 +82691,7 @@ void main() {
|
|
|
79887
82691
|
exports.MeshNodeTool = MeshNodeTool;
|
|
79888
82692
|
exports.NrrdTools = NrrdTools;
|
|
79889
82693
|
exports.REVISION = REVISION;
|
|
82694
|
+
exports.SurfaceAnnotator = SurfaceAnnotator;
|
|
79890
82695
|
exports.addBoxHelper = addBoxHelper;
|
|
79891
82696
|
exports.addLabelToScene = addLabelToScene;
|
|
79892
82697
|
exports.configKiwriousHeart = configKiwriousHeart;
|