|
@@ -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;
|