Преглед изворни кода

SVGRenderer: Add near/far plane clipping support. (#32210)

mrdoob пре 5 месеци
родитељ
комит
b03e036acb
2 измењених фајлова са 245 додато и 33 уклоњено
  1. 243 28
      examples/jsm/renderers/Projector.js
  2. 2 5
      examples/jsm/renderers/SVGRenderer.js

+ 243 - 28
examples/jsm/renderers/Projector.js

@@ -139,7 +139,7 @@ class Projector {
 			_face, _faceCount, _facePoolLength = 0,
 			_face, _faceCount, _facePoolLength = 0,
 			_line, _lineCount, _linePoolLength = 0,
 			_line, _lineCount, _linePoolLength = 0,
 			_sprite, _spriteCount, _spritePoolLength = 0,
 			_sprite, _spriteCount, _spritePoolLength = 0,
-			_modelMatrix;
+			_modelMatrix, _clipInput = [], _clipOutput = [];
 
 
 		const
 		const
 
 
@@ -159,8 +159,20 @@ class Projector {
 
 
 			_frustum = new Frustum(),
 			_frustum = new Frustum(),
 
 
-			_objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = [];
+			_objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = [],
 
 
+			_clipVertexPool = [],
+			_clipPos1 = new Vector4(),
+			_clipPos2 = new Vector4(),
+			_clipPos3 = new Vector4(),
+			_screenVertexPool = [],
+			_clipInputVertices = [ null, null, null ],
+
+			_clipPlanes = [
+				{ sign: + 1 },
+				{ sign: - 1 }
+			];
+		
 		//
 		//
 
 
 		function RenderList() {
 		function RenderList() {
@@ -298,48 +310,165 @@ class Projector {
 				const v2 = _vertexPool[ b ];
 				const v2 = _vertexPool[ b ];
 				const v3 = _vertexPool[ c ];
 				const v3 = _vertexPool[ c ];
 
 
-				if ( checkTriangleVisibility( v1, v2, v3 ) === false ) return;
+				// Get homogeneous clip space positions (before perspective divide)
+				_clipPos1.copy( v1.positionWorld ).applyMatrix4( _viewProjectionMatrix );
+				_clipPos2.copy( v2.positionWorld ).applyMatrix4( _viewProjectionMatrix );
+				_clipPos3.copy( v3.positionWorld ).applyMatrix4( _viewProjectionMatrix );
+
+				// Check if triangle needs clipping
+				const nearDist1 = _clipPos1.z + _clipPos1.w;
+				const nearDist2 = _clipPos2.z + _clipPos2.w;
+				const nearDist3 = _clipPos3.z + _clipPos3.w;
+				const farDist1 = - _clipPos1.z + _clipPos1.w;
+				const farDist2 = - _clipPos2.z + _clipPos2.w;
+				const farDist3 = - _clipPos3.z + _clipPos3.w;
+
+				// Check if completely outside
+				if ( ( nearDist1 < 0 && nearDist2 < 0 && nearDist3 < 0 ) ||
+					( farDist1 < 0 && farDist2 < 0 && farDist3 < 0 ) ) {
+
+					return; // Triangle completely clipped
+
+				}
+
+				// Check if completely inside (no clipping needed)
+				if ( nearDist1 >= 0 && nearDist2 >= 0 && nearDist3 >= 0 &&
+					farDist1 >= 0 && farDist2 >= 0 && farDist3 >= 0 ) {
+
+					// No clipping needed - use original path
+					if ( checkTriangleVisibility( v1, v2, v3 ) === false ) return;
+
+					if ( material.side === DoubleSide || checkBackfaceCulling( v1, v2, v3 ) === true ) {
+
+						_face = getNextFaceInPool();
 
 
-				if ( material.side === DoubleSide || checkBackfaceCulling( v1, v2, v3 ) === true ) {
+						_face.id = object.id;
+						_face.v1.copy( v1 );
+						_face.v2.copy( v2 );
+						_face.v3.copy( v3 );
+						_face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3;
+						_face.renderOrder = object.renderOrder;
+						
+						// face normal
+						_vector3.subVectors( v3.position, v2.position );
+						_vector4.subVectors( v1.position, v2.position );
+						_vector3.cross( _vector4 );
+						_face.normalModel.copy( _vector3 );
+						_face.normalModel.applyMatrix3( normalMatrix ).normalize();
 
 
-					_face = getNextFaceInPool();
+						for ( let i = 0; i < 3; i ++ ) {
 
 
-					_face.id = object.id;
-					_face.v1.copy( v1 );
-					_face.v2.copy( v2 );
-					_face.v3.copy( v3 );
-					_face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3;
-					_face.renderOrder = object.renderOrder;
+							const normal = _face.vertexNormalsModel[ i ];
+							normal.fromArray( normals, arguments[ i ] * 3 );
+							normal.applyMatrix3( normalMatrix ).normalize();
 
 
-					// face normal
-					_vector3.subVectors( v3.position, v2.position );
-					_vector4.subVectors( v1.position, v2.position );
-					_vector3.cross( _vector4 );
-					_face.normalModel.copy( _vector3 );
-					_face.normalModel.applyMatrix3( normalMatrix ).normalize();
+							const uv = _face.uvs[ i ];
+							uv.fromArray( uvs, arguments[ i ] * 2 );
 
 
-					for ( let i = 0; i < 3; i ++ ) {
+						}
+
+						_face.vertexNormalsLength = 3;
+
+						_face.material = material;
+
+						if ( material.vertexColors ) {
+
+							_face.color.fromArray( colors, a * 3 );
 
 
-						const normal = _face.vertexNormalsModel[ i ];
-						normal.fromArray( normals, arguments[ i ] * 3 );
-						normal.applyMatrix3( normalMatrix ).normalize();
+						}
 
 
-						const uv = _face.uvs[ i ];
-						uv.fromArray( uvs, arguments[ i ] * 2 );
+						_renderData.elements.push( _face );
 
 
 					}
 					}
 
 
-					_face.vertexNormalsLength = 3;
+					return;
+
+				}
+
+				// Triangle needs clipping
+				_clipInputVertices[ 0 ] = _clipPos1;
+				_clipInputVertices[ 1 ] = _clipPos2;
+				_clipInputVertices[ 2 ] = _clipPos3;
+				const clippedCount = clipTriangle( _clipInputVertices );
+
+				if ( clippedCount < 3 ) return; // Triangle completely clipped
 
 
-					_face.material = material;
+				// Perform perspective divide on clipped vertices and create screen vertices
+				for ( let i = 0; i < clippedCount; i ++ ) {
 
 
-					if ( material.vertexColors ) {
+					const cv = _clipInput[ i ];
+					
+					// Get or create renderable vertex from pool
+					let sv = _screenVertexPool[ i ];
+					if ( ! sv ) {
 
 
-						_face.color.fromArray( colors, a * 3 );
+						sv = new RenderableVertex();
+						_screenVertexPool[ i ] = sv;
 
 
 					}
 					}
 
 
-					_renderData.elements.push( _face );
+					// Perform perspective divide
+					const invW = 1 / cv.w;
+					sv.positionScreen.set( cv.x * invW, cv.y * invW, cv.z * invW, 1 );
+
+					// Interpolate world position (simplified - using weighted average based on barycentric-like coords)
+					// For a proper implementation, we'd need to track interpolation weights
+					sv.positionWorld.copy( v1.positionWorld );
+
+					sv.visible = true;
+
+				}
+
+				// Triangulate the clipped polygon (simple fan triangulation)
+				for ( let i = 1; i < clippedCount - 1; i ++ ) {
+
+					const tv1 = _screenVertexPool[ 0 ];
+					const tv2 = _screenVertexPool[ i ];
+					const tv3 = _screenVertexPool[ i + 1 ];
+
+					if ( material.side === DoubleSide || checkBackfaceCulling( tv1, tv2, tv3 ) === true ) {
+
+						_face = getNextFaceInPool();
+
+						_face.id = object.id;
+						_face.v1.copy( tv1 );
+						_face.v2.copy( tv2 );
+						_face.v3.copy( tv3 );
+						_face.z = ( tv1.positionScreen.z + tv2.positionScreen.z + tv3.positionScreen.z ) / 3;
+						_face.renderOrder = object.renderOrder;
+
+						// face normal - use original triangle's normal
+						_vector3.subVectors( v3.position, v2.position );
+						_vector4.subVectors( v1.position, v2.position );
+						_vector3.cross( _vector4 );
+						_face.normalModel.copy( _vector3 );
+						_face.normalModel.applyMatrix3( normalMatrix ).normalize();
+
+						// Use original vertex normals and UVs (simplified - proper impl would interpolate)
+						for ( let j = 0; j < 3; j ++ ) {
+
+							const normal = _face.vertexNormalsModel[ j ];
+							normal.fromArray( normals, arguments[ j ] * 3 );
+							normal.applyMatrix3( normalMatrix ).normalize();
+
+							const uv = _face.uvs[ j ];
+							uv.fromArray( uvs, arguments[ j ] * 2 );
+
+						}
+
+						_face.vertexNormalsLength = 3;
+
+						_face.material = material;
+
+						if ( material.vertexColors ) {
+
+							_face.color.fromArray( colors, a * 3 );
+
+						}
+
+						_renderData.elements.push( _face );
+
+					}
 
 
 				}
 				}
 
 
@@ -858,6 +987,92 @@ class Projector {
 
 
 		}
 		}
 
 
+		// Sutherland-Hodgman triangle clipping in homogeneous clip space
+		// Returns count of vertices in clipped polygon (0 if completely clipped, 3+ if partially clipped)
+		// Result vertices are in _clipInput array
+		function clipTriangle( vertices ) {
+
+			// Initialize input with the three input vertices
+			_clipInput[ 0 ] = vertices[ 0 ];
+			_clipInput[ 1 ] = vertices[ 1 ];
+			_clipInput[ 2 ] = vertices[ 2 ];
+
+			let inputCount = 3;
+			let outputCount = 0;
+
+			for ( let p = 0; p < _clipPlanes.length; p ++ ) {
+
+				const plane = _clipPlanes[ p ];
+				outputCount = 0;
+
+				if ( inputCount === 0 ) break;
+
+				for ( let i = 0; i < inputCount; i ++ ) {
+
+					const v1 = _clipInput[ i ];
+					const v2 = _clipInput[ ( i + 1 ) % inputCount ];
+
+					const d1 = plane.sign * v1.z + v1.w;
+					const d2 = plane.sign * v2.z + v2.w;
+
+					const v1Inside = d1 >= 0;
+					const v2Inside = d2 >= 0;
+
+					if ( v1Inside && v2Inside ) {
+
+						// Both inside - add v1
+						_clipOutput[ outputCount ++ ] = v1;
+
+					} else if ( v1Inside && ! v2Inside ) {
+
+						// v1 inside, v2 outside - add v1 and intersection
+						_clipOutput[ outputCount ++ ] = v1;
+
+						const t = d1 / ( d1 - d2 );
+						let intersection = _clipVertexPool[ outputCount ];
+						if ( ! intersection ) {
+
+							intersection = new Vector4();
+							_clipVertexPool[ outputCount ] = intersection;
+
+						}
+
+						intersection.lerpVectors( v1, v2, t );
+						_clipOutput[ outputCount ++ ] = intersection;
+
+					} else if ( ! v1Inside && v2Inside ) {
+
+						// v1 outside, v2 inside - add intersection only
+						const t = d1 / ( d1 - d2 );
+						let intersection = _clipVertexPool[ outputCount ];
+						if ( ! intersection ) {
+
+							intersection = new Vector4();
+							_clipVertexPool[ outputCount ] = intersection;
+
+						}
+
+						intersection.lerpVectors( v1, v2, t );
+						_clipOutput[ outputCount ++ ] = intersection;
+
+					}
+
+					// Both outside - add nothing
+
+				}
+
+				// Swap input/output
+				const temp = _clipInput;
+				_clipInput = _clipOutput;
+				_clipOutput = temp;
+				inputCount = outputCount;
+
+			}
+
+			return inputCount;
+
+		}
+	
 		function clipLine( s1, s2 ) {
 		function clipLine( s1, s2 ) {
 
 
 			let alpha1 = 0, alpha2 = 1;
 			let alpha1 = 0, alpha2 = 1;

+ 2 - 5
examples/jsm/renderers/SVGRenderer.js

@@ -8,12 +8,13 @@ import {
 	SRGBColorSpace,
 	SRGBColorSpace,
 	Vector3
 	Vector3
 } from 'three';
 } from 'three';
+
 import {
 import {
 	Projector,
 	Projector,
 	RenderableFace,
 	RenderableFace,
 	RenderableLine,
 	RenderableLine,
 	RenderableSprite
 	RenderableSprite
-} from '../renderers/Projector.js';
+} from './Projector.js';
 
 
 /**
 /**
  * Can be used to wrap SVG elements into a 3D object.
  * Can be used to wrap SVG elements into a 3D object.
@@ -367,10 +368,6 @@ class SVGRenderer {
 
 
 					_v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
 					_v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
 
 
-					if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue;
-					if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue;
-					if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue;
-
 					_v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
 					_v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
 					_v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
 					_v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
 					_v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
 					_v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;

粤ICP备19079148号