|
|
@@ -80,25 +80,87 @@ function TubePainter() {
|
|
|
const color = new Color( 0xffffff );
|
|
|
let size = 1;
|
|
|
|
|
|
- function stroke( position1, position2, matrix1, matrix2 ) {
|
|
|
-
|
|
|
- if ( position1.distanceToSquared( position2 ) === 0 ) return;
|
|
|
+ function addCap( position, matrix, isEndCap, capSize ) {
|
|
|
|
|
|
let count = geometry.drawRange.count;
|
|
|
|
|
|
- const points = getPoints( size );
|
|
|
+ const points = getPoints( capSize );
|
|
|
+ const center = position.clone();
|
|
|
+
|
|
|
+ const capNormal = new Vector3();
|
|
|
+ capNormal.set(
|
|
|
+ matrix.elements[ 8 ],
|
|
|
+ matrix.elements[ 9 ],
|
|
|
+ matrix.elements[ 10 ]
|
|
|
+ );
|
|
|
+
|
|
|
+ if ( isEndCap ) {
|
|
|
+
|
|
|
+ capNormal.negate();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ capNormal.normalize();
|
|
|
|
|
|
for ( let i = 0, il = points.length; i < il; i ++ ) {
|
|
|
|
|
|
const vertex1 = points[ i ];
|
|
|
const vertex2 = points[ ( i + 1 ) % il ];
|
|
|
|
|
|
- // positions
|
|
|
+ if ( isEndCap ) {
|
|
|
+
|
|
|
+ vector1.copy( center );
|
|
|
+ vector2.copy( vertex1 ).applyMatrix4( matrix ).add( position );
|
|
|
+ vector3.copy( vertex2 ).applyMatrix4( matrix ).add( position );
|
|
|
+
|
|
|
+ } else {
|
|
|
|
|
|
- vector1.copy( vertex1 ).applyMatrix4( matrix2 ).add( position2 );
|
|
|
- vector2.copy( vertex2 ).applyMatrix4( matrix2 ).add( position2 );
|
|
|
- vector3.copy( vertex2 ).applyMatrix4( matrix1 ).add( position1 );
|
|
|
- vector4.copy( vertex1 ).applyMatrix4( matrix1 ).add( position1 );
|
|
|
+ vector1.copy( center );
|
|
|
+ vector2.copy( vertex2 ).applyMatrix4( matrix ).add( position );
|
|
|
+ vector3.copy( vertex1 ).applyMatrix4( matrix ).add( position );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ vector1.toArray( positions.array, ( count + 0 ) * 3 );
|
|
|
+ vector2.toArray( positions.array, ( count + 1 ) * 3 );
|
|
|
+ vector3.toArray( positions.array, ( count + 2 ) * 3 );
|
|
|
+
|
|
|
+ capNormal.toArray( normals.array, ( count + 0 ) * 3 );
|
|
|
+ capNormal.toArray( normals.array, ( count + 1 ) * 3 );
|
|
|
+ capNormal.toArray( normals.array, ( count + 2 ) * 3 );
|
|
|
+
|
|
|
+ color.toArray( colors.array, ( count + 0 ) * 3 );
|
|
|
+ color.toArray( colors.array, ( count + 1 ) * 3 );
|
|
|
+ color.toArray( colors.array, ( count + 2 ) * 3 );
|
|
|
+
|
|
|
+ count += 3;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ geometry.drawRange.count = count;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function stroke( position1, position2, matrix1, matrix2, size1, size2 ) {
|
|
|
+
|
|
|
+ if ( position1.distanceToSquared( position2 ) === 0 ) return;
|
|
|
+
|
|
|
+ let count = geometry.drawRange.count;
|
|
|
+
|
|
|
+ const points1 = getPoints( size1 );
|
|
|
+ const points2 = getPoints( size2 );
|
|
|
+
|
|
|
+ for ( let i = 0, il = points2.length; i < il; i ++ ) {
|
|
|
+
|
|
|
+ const vertex1_2 = points2[ i ];
|
|
|
+ const vertex2_2 = points2[ ( i + 1 ) % il ];
|
|
|
+ const vertex1_1 = points1[ i ];
|
|
|
+ const vertex2_1 = points1[ ( i + 1 ) % il ];
|
|
|
+
|
|
|
+ vector1.copy( vertex1_2 ).applyMatrix4( matrix2 ).add( position2 );
|
|
|
+ vector2.copy( vertex2_2 ).applyMatrix4( matrix2 ).add( position2 );
|
|
|
+ vector3.copy( vertex2_1 ).applyMatrix4( matrix1 ).add( position1 );
|
|
|
+ vector4.copy( vertex1_1 ).applyMatrix4( matrix1 ).add( position1 );
|
|
|
|
|
|
vector1.toArray( positions.array, ( count + 0 ) * 3 );
|
|
|
vector2.toArray( positions.array, ( count + 1 ) * 3 );
|
|
|
@@ -108,12 +170,10 @@ function TubePainter() {
|
|
|
vector3.toArray( positions.array, ( count + 4 ) * 3 );
|
|
|
vector4.toArray( positions.array, ( count + 5 ) * 3 );
|
|
|
|
|
|
- // normals
|
|
|
-
|
|
|
- vector1.copy( vertex1 ).applyMatrix4( matrix2 ).normalize();
|
|
|
- vector2.copy( vertex2 ).applyMatrix4( matrix2 ).normalize();
|
|
|
- vector3.copy( vertex2 ).applyMatrix4( matrix1 ).normalize();
|
|
|
- vector4.copy( vertex1 ).applyMatrix4( matrix1 ).normalize();
|
|
|
+ vector1.copy( vertex1_2 ).applyMatrix4( matrix2 ).normalize();
|
|
|
+ vector2.copy( vertex2_2 ).applyMatrix4( matrix2 ).normalize();
|
|
|
+ vector3.copy( vertex2_1 ).applyMatrix4( matrix1 ).normalize();
|
|
|
+ vector4.copy( vertex1_1 ).applyMatrix4( matrix1 ).normalize();
|
|
|
|
|
|
vector1.toArray( normals.array, ( count + 0 ) * 3 );
|
|
|
vector2.toArray( normals.array, ( count + 1 ) * 3 );
|
|
|
@@ -123,8 +183,6 @@ function TubePainter() {
|
|
|
vector3.toArray( normals.array, ( count + 4 ) * 3 );
|
|
|
vector4.toArray( normals.array, ( count + 5 ) * 3 );
|
|
|
|
|
|
- // colors
|
|
|
-
|
|
|
color.toArray( colors.array, ( count + 0 ) * 3 );
|
|
|
color.toArray( colors.array, ( count + 1 ) * 3 );
|
|
|
color.toArray( colors.array, ( count + 2 ) * 3 );
|
|
|
@@ -143,7 +201,9 @@ function TubePainter() {
|
|
|
|
|
|
//
|
|
|
|
|
|
- const up = new Vector3( 0, 1, 0 );
|
|
|
+ const direction = new Vector3();
|
|
|
+ const side = new Vector3();
|
|
|
+ const normal = new Vector3();
|
|
|
|
|
|
const point1 = new Vector3();
|
|
|
const point2 = new Vector3();
|
|
|
@@ -151,25 +211,275 @@ function TubePainter() {
|
|
|
const matrix1 = new Matrix4();
|
|
|
const matrix2 = new Matrix4();
|
|
|
|
|
|
+ const lastNormal = new Vector3( 0, 1, 0 );
|
|
|
+ const prevDirection = new Vector3( 0, 0, 1 );
|
|
|
+ const rotationAxis = new Vector3();
|
|
|
+
|
|
|
+ let isFirstSegment = true;
|
|
|
+ let isDrawing = false;
|
|
|
+ let hasSegments = false;
|
|
|
+
|
|
|
+ let segmentStartIndex = null;
|
|
|
+ let lastSegmentPosition = new Vector3();
|
|
|
+ let lastSegmentSize = 1;
|
|
|
+ let nextSegmentStartSize = 1;
|
|
|
+
|
|
|
+ function calculateRMF( applySmoothing ) {
|
|
|
+
|
|
|
+ if ( isFirstSegment === true ) {
|
|
|
+
|
|
|
+ if ( Math.abs( direction.y ) < 0.99 ) {
|
|
|
+
|
|
|
+ normal.set( 0, 1, 0 ).sub( direction.clone().multiplyScalar( direction.y ) ).normalize();
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ normal.set( 1, 0, 0 ).sub( direction.clone().multiplyScalar( direction.x ) ).normalize();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ rotationAxis.crossVectors( prevDirection, direction );
|
|
|
+ const rotAxisLength = rotationAxis.length();
|
|
|
+
|
|
|
+ if ( rotAxisLength > 0.0001 ) {
|
|
|
+
|
|
|
+ rotationAxis.divideScalar( rotAxisLength );
|
|
|
+ const c1 = - 2.0 / ( 1.0 + prevDirection.dot( direction ) );
|
|
|
+ const temp = new Vector3().addVectors( prevDirection, direction );
|
|
|
+ const dot = lastNormal.dot( temp );
|
|
|
+ normal.copy( lastNormal ).addScaledVector( temp, c1 * dot );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ normal.copy( lastNormal );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ side.crossVectors( direction, normal ).normalize();
|
|
|
+ normal.crossVectors( side, direction ).normalize();
|
|
|
+
|
|
|
+ if ( applySmoothing === true && isFirstSegment === false ) {
|
|
|
+
|
|
|
+ const smoothFactor = 0.3;
|
|
|
+ normal.lerp( lastNormal, smoothFactor ).normalize();
|
|
|
+ side.crossVectors( direction, normal ).normalize();
|
|
|
+ normal.crossVectors( side, direction ).normalize();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ lastNormal.copy( normal );
|
|
|
+ prevDirection.copy( direction );
|
|
|
+
|
|
|
+ matrix1.makeBasis( side, normal, direction.clone().negate() );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
function moveTo( position ) {
|
|
|
|
|
|
- point1.copy( position );
|
|
|
- matrix1.lookAt( point2, point1, up );
|
|
|
+ if ( isDrawing ) {
|
|
|
+
|
|
|
+ if ( segmentStartIndex !== null ) {
|
|
|
+
|
|
|
+ addCap( point1, matrix1, true, size );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ addCap( point2, matrix2, true, size );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ update();
|
|
|
+
|
|
|
+ isDrawing = false;
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
point2.copy( position );
|
|
|
- matrix2.copy( matrix1 );
|
|
|
+
|
|
|
+ lastNormal.set( 0, 1, 0 );
|
|
|
+
|
|
|
+ segmentStartIndex = null;
|
|
|
+ lastSegmentPosition.copy( position );
|
|
|
+ lastSegmentSize = size;
|
|
|
+
|
|
|
+ isFirstSegment = true;
|
|
|
+ hasSegments = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
function lineTo( position ) {
|
|
|
|
|
|
+ isDrawing = true;
|
|
|
+
|
|
|
point1.copy( position );
|
|
|
- matrix1.lookAt( point2, point1, up );
|
|
|
|
|
|
- stroke( point1, point2, matrix1, matrix2 );
|
|
|
+ const fromPos = segmentStartIndex === null ? point2 : lastSegmentPosition;
|
|
|
+ direction.subVectors( point1, fromPos );
|
|
|
+ const length = direction.length();
|
|
|
+
|
|
|
+ if ( length === 0 ) return;
|
|
|
|
|
|
- point2.copy( point1 );
|
|
|
- matrix2.copy( matrix1 );
|
|
|
+ const minDistance = 0.01 * size;
|
|
|
+ const shouldCommit = length >= minDistance;
|
|
|
+
|
|
|
+ if ( segmentStartIndex === null ) {
|
|
|
+
|
|
|
+ nextSegmentStartSize = isFirstSegment ? size : lastSegmentSize;
|
|
|
+
|
|
|
+ lineToInternal( point1 );
|
|
|
+ const afterCount = geometry.drawRange.count;
|
|
|
+
|
|
|
+ const points = getPoints( size );
|
|
|
+ const segmentVertices = points.length * 6;
|
|
|
+ segmentStartIndex = afterCount - segmentVertices;
|
|
|
+
|
|
|
+ if ( shouldCommit === false ) {
|
|
|
+
|
|
|
+ lastSegmentPosition.copy( fromPos );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ updatePendingSegment( point1 );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( shouldCommit ) {
|
|
|
+
|
|
|
+ if ( hasSegments ) {
|
|
|
+
|
|
|
+ smoothConnectionNormals();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ point2.copy( point1 );
|
|
|
+ matrix2.copy( matrix1 );
|
|
|
+
|
|
|
+ lastSegmentPosition.copy( point1 );
|
|
|
+ lastSegmentSize = size;
|
|
|
+ segmentStartIndex = null;
|
|
|
+ hasSegments = true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function smoothConnectionNormals() {
|
|
|
+
|
|
|
+ if ( segmentStartIndex === null ) return;
|
|
|
+
|
|
|
+ const points = getPoints( size );
|
|
|
+ const segmentVertexCount = points.length * 6;
|
|
|
+
|
|
|
+ const prevSegmentIndex = segmentStartIndex - segmentVertexCount;
|
|
|
+
|
|
|
+ for ( let i = 0; i < points.length; i ++ ) {
|
|
|
+
|
|
|
+ const prevIdx1 = prevSegmentIndex + i * 6 + 2;
|
|
|
+ const prevIdx2 = prevSegmentIndex + i * 6 + 5;
|
|
|
+ const currIdx1 = segmentStartIndex + i * 6 + 0;
|
|
|
+
|
|
|
+ const avgNormal = new Vector3();
|
|
|
+
|
|
|
+ avgNormal.set(
|
|
|
+ normals.array[ prevIdx1 * 3 + 0 ],
|
|
|
+ normals.array[ prevIdx1 * 3 + 1 ],
|
|
|
+ normals.array[ prevIdx1 * 3 + 2 ]
|
|
|
+ );
|
|
|
+
|
|
|
+ avgNormal.add( new Vector3(
|
|
|
+ normals.array[ currIdx1 * 3 + 0 ],
|
|
|
+ normals.array[ currIdx1 * 3 + 1 ],
|
|
|
+ normals.array[ currIdx1 * 3 + 2 ]
|
|
|
+ ) );
|
|
|
+
|
|
|
+ avgNormal.normalize();
|
|
|
+
|
|
|
+ avgNormal.toArray( normals.array, prevIdx1 * 3 );
|
|
|
+ avgNormal.toArray( normals.array, prevIdx2 * 3 );
|
|
|
+ avgNormal.toArray( normals.array, currIdx1 * 3 );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ normals.addUpdateRange( prevSegmentIndex * 3, segmentVertexCount * 3 );
|
|
|
+ normals.addUpdateRange( segmentStartIndex * 3, segmentVertexCount * 3 );
|
|
|
+ normals.needsUpdate = true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function updatePendingSegment( position ) {
|
|
|
+
|
|
|
+ point1.copy( position );
|
|
|
+
|
|
|
+ direction.subVectors( point1, point2 );
|
|
|
+ const length = direction.length();
|
|
|
+
|
|
|
+ if ( length === 0 ) return;
|
|
|
+
|
|
|
+ direction.divideScalar( length );
|
|
|
+
|
|
|
+ calculateRMF( true );
|
|
|
+
|
|
|
+ const currentPoints = getPoints( size );
|
|
|
+ const previousPoints = getPoints( nextSegmentStartSize );
|
|
|
+ let vertexIndex = segmentStartIndex;
|
|
|
+
|
|
|
+ for ( let i = 0, il = currentPoints.length; i < il; i ++ ) {
|
|
|
+
|
|
|
+ const currentVertex1 = currentPoints[ i ];
|
|
|
+ const currentVertex2 = currentPoints[ ( i + 1 ) % il ];
|
|
|
+ const previousVertex1 = previousPoints[ i ];
|
|
|
+ const previousVertex2 = previousPoints[ ( i + 1 ) % il ];
|
|
|
+
|
|
|
+ vector1.copy( previousVertex1 ).applyMatrix4( matrix2 ).add( point2 );
|
|
|
+ vector2.copy( previousVertex2 ).applyMatrix4( matrix2 ).add( point2 );
|
|
|
+ vector3.copy( currentVertex2 ).applyMatrix4( matrix1 ).add( point1 );
|
|
|
+ vector4.copy( currentVertex1 ).applyMatrix4( matrix1 ).add( point1 );
|
|
|
+
|
|
|
+ vector1.toArray( positions.array, ( vertexIndex + 0 ) * 3 );
|
|
|
+ vector2.toArray( positions.array, ( vertexIndex + 1 ) * 3 );
|
|
|
+ vector4.toArray( positions.array, ( vertexIndex + 2 ) * 3 );
|
|
|
+
|
|
|
+ vector2.toArray( positions.array, ( vertexIndex + 3 ) * 3 );
|
|
|
+ vector3.toArray( positions.array, ( vertexIndex + 4 ) * 3 );
|
|
|
+ vector4.toArray( positions.array, ( vertexIndex + 5 ) * 3 );
|
|
|
+
|
|
|
+ vertexIndex += 6;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ positions.addUpdateRange( segmentStartIndex * 3, ( vertexIndex - segmentStartIndex ) * 3 );
|
|
|
+ positions.needsUpdate = true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function lineToInternal( position ) {
|
|
|
+
|
|
|
+ point1.copy( position );
|
|
|
+
|
|
|
+ direction.subVectors( point1, point2 );
|
|
|
+ const length = direction.length();
|
|
|
+
|
|
|
+ if ( length === 0 ) return;
|
|
|
+
|
|
|
+ direction.divideScalar( length );
|
|
|
+
|
|
|
+ calculateRMF( true );
|
|
|
+
|
|
|
+ if ( isFirstSegment === true ) {
|
|
|
+
|
|
|
+ matrix2.copy( matrix1 );
|
|
|
+ addCap( point2, matrix2, false, nextSegmentStartSize );
|
|
|
+ isFirstSegment = false;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ stroke( point1, point2, matrix1, matrix2, size, nextSegmentStartSize );
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -179,6 +489,12 @@ function TubePainter() {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ function setColor( value ) {
|
|
|
+
|
|
|
+ color.copy( value );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
//
|
|
|
|
|
|
let count = 0;
|
|
|
@@ -241,6 +557,15 @@ function TubePainter() {
|
|
|
*/
|
|
|
setSize: setSize,
|
|
|
|
|
|
+ /**
|
|
|
+ * Sets the color of newly rendered tube segments.
|
|
|
+ *
|
|
|
+ * @method
|
|
|
+ * @name TubePainter#setColor
|
|
|
+ * @param {Color} color The color.
|
|
|
+ */
|
|
|
+ setColor: setColor,
|
|
|
+
|
|
|
/**
|
|
|
* Updates the internal geometry buffers so the new painted
|
|
|
* segments are rendered.
|