@@ -139,7 +139,7 @@ class Projector {
139139 _face , _faceCount , _facePoolLength = 0 ,
140140 _line , _lineCount , _linePoolLength = 0 ,
141141 _sprite , _spriteCount , _spritePoolLength = 0 ,
142- _modelMatrix ;
142+ _modelMatrix , _clipInput = [ ] , _clipOutput = [ ] ;
143143
144144 const
145145
@@ -159,8 +159,20 @@ class Projector {
159159
160160 _frustum = new Frustum ( ) ,
161161
162- _objectPool = [ ] , _vertexPool = [ ] , _facePool = [ ] , _linePool = [ ] , _spritePool = [ ] ;
162+ _objectPool = [ ] , _vertexPool = [ ] , _facePool = [ ] , _linePool = [ ] , _spritePool = [ ] ,
163163
164+ _clipVertexPool = [ ] ,
165+ _clipPos1 = new Vector4 ( ) ,
166+ _clipPos2 = new Vector4 ( ) ,
167+ _clipPos3 = new Vector4 ( ) ,
168+ _screenVertexPool = [ ] ,
169+ _clipInputVertices = [ null , null , null ] ,
170+
171+ _clipPlanes = [
172+ { sign : + 1 } ,
173+ { sign : - 1 }
174+ ] ;
175+
164176 //
165177
166178 function RenderList ( ) {
@@ -298,48 +310,165 @@ class Projector {
298310 const v2 = _vertexPool [ b ] ;
299311 const v3 = _vertexPool [ c ] ;
300312
301- if ( checkTriangleVisibility ( v1 , v2 , v3 ) === false ) return ;
313+ // Get homogeneous clip space positions (before perspective divide)
314+ _clipPos1 . copy ( v1 . positionWorld ) . applyMatrix4 ( _viewProjectionMatrix ) ;
315+ _clipPos2 . copy ( v2 . positionWorld ) . applyMatrix4 ( _viewProjectionMatrix ) ;
316+ _clipPos3 . copy ( v3 . positionWorld ) . applyMatrix4 ( _viewProjectionMatrix ) ;
317+
318+ // Check if triangle needs clipping
319+ const nearDist1 = _clipPos1 . z + _clipPos1 . w ;
320+ const nearDist2 = _clipPos2 . z + _clipPos2 . w ;
321+ const nearDist3 = _clipPos3 . z + _clipPos3 . w ;
322+ const farDist1 = - _clipPos1 . z + _clipPos1 . w ;
323+ const farDist2 = - _clipPos2 . z + _clipPos2 . w ;
324+ const farDist3 = - _clipPos3 . z + _clipPos3 . w ;
325+
326+ // Check if completely outside
327+ if ( ( nearDist1 < 0 && nearDist2 < 0 && nearDist3 < 0 ) ||
328+ ( farDist1 < 0 && farDist2 < 0 && farDist3 < 0 ) ) {
329+
330+ return ; // Triangle completely clipped
331+
332+ }
333+
334+ // Check if completely inside (no clipping needed)
335+ if ( nearDist1 >= 0 && nearDist2 >= 0 && nearDist3 >= 0 &&
336+ farDist1 >= 0 && farDist2 >= 0 && farDist3 >= 0 ) {
337+
338+ // No clipping needed - use original path
339+ if ( checkTriangleVisibility ( v1 , v2 , v3 ) === false ) return ;
340+
341+ if ( material . side === DoubleSide || checkBackfaceCulling ( v1 , v2 , v3 ) === true ) {
342+
343+ _face = getNextFaceInPool ( ) ;
302344
303- if ( material . side === DoubleSide || checkBackfaceCulling ( v1 , v2 , v3 ) === true ) {
345+ _face . id = object . id ;
346+ _face . v1 . copy ( v1 ) ;
347+ _face . v2 . copy ( v2 ) ;
348+ _face . v3 . copy ( v3 ) ;
349+ _face . z = ( v1 . positionScreen . z + v2 . positionScreen . z + v3 . positionScreen . z ) / 3 ;
350+ _face . renderOrder = object . renderOrder ;
351+
352+ // face normal
353+ _vector3 . subVectors ( v3 . position , v2 . position ) ;
354+ _vector4 . subVectors ( v1 . position , v2 . position ) ;
355+ _vector3 . cross ( _vector4 ) ;
356+ _face . normalModel . copy ( _vector3 ) ;
357+ _face . normalModel . applyMatrix3 ( normalMatrix ) . normalize ( ) ;
304358
305- _face = getNextFaceInPool ( ) ;
359+ for ( let i = 0 ; i < 3 ; i ++ ) {
306360
307- _face . id = object . id ;
308- _face . v1 . copy ( v1 ) ;
309- _face . v2 . copy ( v2 ) ;
310- _face . v3 . copy ( v3 ) ;
311- _face . z = ( v1 . positionScreen . z + v2 . positionScreen . z + v3 . positionScreen . z ) / 3 ;
312- _face . renderOrder = object . renderOrder ;
361+ const normal = _face . vertexNormalsModel [ i ] ;
362+ normal . fromArray ( normals , arguments [ i ] * 3 ) ;
363+ normal . applyMatrix3 ( normalMatrix ) . normalize ( ) ;
313364
314- // face normal
315- _vector3 . subVectors ( v3 . position , v2 . position ) ;
316- _vector4 . subVectors ( v1 . position , v2 . position ) ;
317- _vector3 . cross ( _vector4 ) ;
318- _face . normalModel . copy ( _vector3 ) ;
319- _face . normalModel . applyMatrix3 ( normalMatrix ) . normalize ( ) ;
365+ const uv = _face . uvs [ i ] ;
366+ uv . fromArray ( uvs , arguments [ i ] * 2 ) ;
320367
321- for ( let i = 0 ; i < 3 ; i ++ ) {
368+ }
369+
370+ _face . vertexNormalsLength = 3 ;
371+
372+ _face . material = material ;
373+
374+ if ( material . vertexColors ) {
375+
376+ _face . color . fromArray ( colors , a * 3 ) ;
322377
323- const normal = _face . vertexNormalsModel [ i ] ;
324- normal . fromArray ( normals , arguments [ i ] * 3 ) ;
325- normal . applyMatrix3 ( normalMatrix ) . normalize ( ) ;
378+ }
326379
327- const uv = _face . uvs [ i ] ;
328- uv . fromArray ( uvs , arguments [ i ] * 2 ) ;
380+ _renderData . elements . push ( _face ) ;
329381
330382 }
331383
332- _face . vertexNormalsLength = 3 ;
384+ return ;
385+
386+ }
387+
388+ // Triangle needs clipping
389+ _clipInputVertices [ 0 ] = _clipPos1 ;
390+ _clipInputVertices [ 1 ] = _clipPos2 ;
391+ _clipInputVertices [ 2 ] = _clipPos3 ;
392+ const clippedCount = clipTriangle ( _clipInputVertices ) ;
393+
394+ if ( clippedCount < 3 ) return ; // Triangle completely clipped
333395
334- _face . material = material ;
396+ // Perform perspective divide on clipped vertices and create screen vertices
397+ for ( let i = 0 ; i < clippedCount ; i ++ ) {
335398
336- if ( material . vertexColors ) {
399+ const cv = _clipInput [ i ] ;
400+
401+ // Get or create renderable vertex from pool
402+ let sv = _screenVertexPool [ i ] ;
403+ if ( ! sv ) {
337404
338- _face . color . fromArray ( colors , a * 3 ) ;
405+ sv = new RenderableVertex ( ) ;
406+ _screenVertexPool [ i ] = sv ;
339407
340408 }
341409
342- _renderData . elements . push ( _face ) ;
410+ // Perform perspective divide
411+ const invW = 1 / cv . w ;
412+ sv . positionScreen . set ( cv . x * invW , cv . y * invW , cv . z * invW , 1 ) ;
413+
414+ // Interpolate world position (simplified - using weighted average based on barycentric-like coords)
415+ // For a proper implementation, we'd need to track interpolation weights
416+ sv . positionWorld . copy ( v1 . positionWorld ) ;
417+
418+ sv . visible = true ;
419+
420+ }
421+
422+ // Triangulate the clipped polygon (simple fan triangulation)
423+ for ( let i = 1 ; i < clippedCount - 1 ; i ++ ) {
424+
425+ const tv1 = _screenVertexPool [ 0 ] ;
426+ const tv2 = _screenVertexPool [ i ] ;
427+ const tv3 = _screenVertexPool [ i + 1 ] ;
428+
429+ if ( material . side === DoubleSide || checkBackfaceCulling ( tv1 , tv2 , tv3 ) === true ) {
430+
431+ _face = getNextFaceInPool ( ) ;
432+
433+ _face . id = object . id ;
434+ _face . v1 . copy ( tv1 ) ;
435+ _face . v2 . copy ( tv2 ) ;
436+ _face . v3 . copy ( tv3 ) ;
437+ _face . z = ( tv1 . positionScreen . z + tv2 . positionScreen . z + tv3 . positionScreen . z ) / 3 ;
438+ _face . renderOrder = object . renderOrder ;
439+
440+ // face normal - use original triangle's normal
441+ _vector3 . subVectors ( v3 . position , v2 . position ) ;
442+ _vector4 . subVectors ( v1 . position , v2 . position ) ;
443+ _vector3 . cross ( _vector4 ) ;
444+ _face . normalModel . copy ( _vector3 ) ;
445+ _face . normalModel . applyMatrix3 ( normalMatrix ) . normalize ( ) ;
446+
447+ // Use original vertex normals and UVs (simplified - proper impl would interpolate)
448+ for ( let j = 0 ; j < 3 ; j ++ ) {
449+
450+ const normal = _face . vertexNormalsModel [ j ] ;
451+ normal . fromArray ( normals , arguments [ j ] * 3 ) ;
452+ normal . applyMatrix3 ( normalMatrix ) . normalize ( ) ;
453+
454+ const uv = _face . uvs [ j ] ;
455+ uv . fromArray ( uvs , arguments [ j ] * 2 ) ;
456+
457+ }
458+
459+ _face . vertexNormalsLength = 3 ;
460+
461+ _face . material = material ;
462+
463+ if ( material . vertexColors ) {
464+
465+ _face . color . fromArray ( colors , a * 3 ) ;
466+
467+ }
468+
469+ _renderData . elements . push ( _face ) ;
470+
471+ }
343472
344473 }
345474
@@ -858,6 +987,92 @@ class Projector {
858987
859988 }
860989
990+ // Sutherland-Hodgman triangle clipping in homogeneous clip space
991+ // Returns count of vertices in clipped polygon (0 if completely clipped, 3+ if partially clipped)
992+ // Result vertices are in _clipInput array
993+ function clipTriangle ( vertices ) {
994+
995+ // Initialize input with the three input vertices
996+ _clipInput [ 0 ] = vertices [ 0 ] ;
997+ _clipInput [ 1 ] = vertices [ 1 ] ;
998+ _clipInput [ 2 ] = vertices [ 2 ] ;
999+
1000+ let inputCount = 3 ;
1001+ let outputCount = 0 ;
1002+
1003+ for ( let p = 0 ; p < _clipPlanes . length ; p ++ ) {
1004+
1005+ const plane = _clipPlanes [ p ] ;
1006+ outputCount = 0 ;
1007+
1008+ if ( inputCount === 0 ) break ;
1009+
1010+ for ( let i = 0 ; i < inputCount ; i ++ ) {
1011+
1012+ const v1 = _clipInput [ i ] ;
1013+ const v2 = _clipInput [ ( i + 1 ) % inputCount ] ;
1014+
1015+ const d1 = plane . sign * v1 . z + v1 . w ;
1016+ const d2 = plane . sign * v2 . z + v2 . w ;
1017+
1018+ const v1Inside = d1 >= 0 ;
1019+ const v2Inside = d2 >= 0 ;
1020+
1021+ if ( v1Inside && v2Inside ) {
1022+
1023+ // Both inside - add v1
1024+ _clipOutput [ outputCount ++ ] = v1 ;
1025+
1026+ } else if ( v1Inside && ! v2Inside ) {
1027+
1028+ // v1 inside, v2 outside - add v1 and intersection
1029+ _clipOutput [ outputCount ++ ] = v1 ;
1030+
1031+ const t = d1 / ( d1 - d2 ) ;
1032+ let intersection = _clipVertexPool [ outputCount ] ;
1033+ if ( ! intersection ) {
1034+
1035+ intersection = new Vector4 ( ) ;
1036+ _clipVertexPool [ outputCount ] = intersection ;
1037+
1038+ }
1039+
1040+ intersection . lerpVectors ( v1 , v2 , t ) ;
1041+ _clipOutput [ outputCount ++ ] = intersection ;
1042+
1043+ } else if ( ! v1Inside && v2Inside ) {
1044+
1045+ // v1 outside, v2 inside - add intersection only
1046+ const t = d1 / ( d1 - d2 ) ;
1047+ let intersection = _clipVertexPool [ outputCount ] ;
1048+ if ( ! intersection ) {
1049+
1050+ intersection = new Vector4 ( ) ;
1051+ _clipVertexPool [ outputCount ] = intersection ;
1052+
1053+ }
1054+
1055+ intersection . lerpVectors ( v1 , v2 , t ) ;
1056+ _clipOutput [ outputCount ++ ] = intersection ;
1057+
1058+ }
1059+
1060+ // Both outside - add nothing
1061+
1062+ }
1063+
1064+ // Swap input/output
1065+ const temp = _clipInput ;
1066+ _clipInput = _clipOutput ;
1067+ _clipOutput = temp ;
1068+ inputCount = outputCount ;
1069+
1070+ }
1071+
1072+ return inputCount ;
1073+
1074+ }
1075+
8611076 function clipLine ( s1 , s2 ) {
8621077
8631078 let alpha1 = 0 , alpha2 = 1 ;
0 commit comments