Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BufferGeometryUtils: Add toCreasedNormals(). #23989

Merged
merged 4 commits into from
Oct 30, 2022
Merged
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
109 changes: 108 additions & 1 deletion examples/jsm/utils/BufferGeometryUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,112 @@ function mergeGroups( geometry ) {

}


// Creates a new, non-indexed geometry with smooth normals everywhere except faces that meet at
// an angle greater than the crease angle.
function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ ) {

const creaseDot = Math.cos( creaseAngle );
const hashMultiplier = ( 1 + 1e-10 ) * 1e2;

// reusable vertors
const verts = [ new Vector3(), new Vector3(), new Vector3() ];
const tempVec1 = new Vector3();
const tempVec2 = new Vector3();
const tempNorm = new Vector3();
const tempNorm2 = new Vector3();

// hashes a vector
function hashVertex( v ) {

const x = ~ ~ ( v.x * hashMultiplier );
const y = ~ ~ ( v.y * hashMultiplier );
const z = ~ ~ ( v.z * hashMultiplier );
return `${x},${y},${z}`;

}

const resultGeometry = geometry.toNonIndexed();
const posAttr = resultGeometry.attributes.position;
const vertexMap = {};

// find all the normals shared by commonly located vertices
for ( let i = 0, l = posAttr.count / 3; i < l; i ++ ) {

const i3 = 3 * i;
const a = verts[ 0 ].fromBufferAttribute( posAttr, i3 + 0 );
const b = verts[ 1 ].fromBufferAttribute( posAttr, i3 + 1 );
const c = verts[ 2 ].fromBufferAttribute( posAttr, i3 + 2 );

tempVec1.subVectors( c, b );
tempVec2.subVectors( a, b );

// add the normal to the map for all vertices
const normal = new Vector3().crossVectors( tempVec1, tempVec2 ).normalize();
for ( let n = 0; n < 3; n ++ ) {

const vert = verts[ n ];
const hash = hashVertex( vert );
if ( ! ( hash in vertexMap ) ) {

vertexMap[ hash ] = [];

}

vertexMap[ hash ].push( normal );

}

}

// average normals from all vertices that share a common location if they are within the
// provided crease threshold
const normalArray = new Float32Array( posAttr.count * 3 );
const normAttr = new BufferAttribute( normalArray, 3, false );
for ( let i = 0, l = posAttr.count / 3; i < l; i ++ ) {

// get the face normal for this vertex
const i3 = 3 * i;
const a = verts[ 0 ].fromBufferAttribute( posAttr, i3 + 0 );
const b = verts[ 1 ].fromBufferAttribute( posAttr, i3 + 1 );
const c = verts[ 2 ].fromBufferAttribute( posAttr, i3 + 2 );

tempVec1.subVectors( c, b );
tempVec2.subVectors( a, b );

tempNorm.crossVectors( tempVec1, tempVec2 ).normalize();

// average all normals that meet the threshold and set the normal value
for ( let n = 0; n < 3; n ++ ) {

const vert = verts[ n ];
const hash = hashVertex( vert );
const otherNormals = vertexMap[ hash ];
tempNorm2.set( 0, 0, 0 );

for ( let k = 0, lk = otherNormals.length; k < lk; k ++ ) {

const otherNorm = otherNormals[ k ];
if ( tempNorm.dot( otherNorm ) > creaseDot ) {

tempNorm2.add( otherNorm );

}

}

tempNorm2.normalize();
normAttr.setXYZ( i3 + n, tempNorm2.x, tempNorm2.y, tempNorm2.z );

}

}

resultGeometry.setAttribute( 'normal', normAttr );
return resultGeometry;

}

export {
computeTangents,
computeMikkTSpaceTangents,
Expand All @@ -1209,5 +1315,6 @@ export {
mergeVertices,
toTrianglesDrawMode,
computeMorphedAttributes,
mergeGroups
mergeGroups,
toCreasedNormals
};