Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 243 additions & 28 deletions examples/jsm/renderers/Projector.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class Projector {
_face, _faceCount, _facePoolLength = 0,
_line, _lineCount, _linePoolLength = 0,
_sprite, _spriteCount, _spritePoolLength = 0,
_modelMatrix;
_modelMatrix, _clipInput = [], _clipOutput = [];

const

Expand All @@ -159,8 +159,20 @@ class Projector {

_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() {
Expand Down Expand Up @@ -298,48 +310,165 @@ class Projector {
const v2 = _vertexPool[ b ];
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 );

}

}

Expand Down Expand Up @@ -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 ) {

let alpha1 = 0, alpha2 = 1;
Expand Down
7 changes: 2 additions & 5 deletions examples/jsm/renderers/SVGRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import {
SRGBColorSpace,
Vector3
} from 'three';

import {
Projector,
RenderableFace,
RenderableLine,
RenderableSprite
} from '../renderers/Projector.js';
} from './Projector.js';

/**
* Can be used to wrap SVG elements into a 3D object.
Expand Down Expand Up @@ -367,10 +368,6 @@ class SVGRenderer {

_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;
_v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
_v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
Expand Down