Skip to content

Incorrect attribute length after calling BufferGeometry.computeVertexNormals with changed positions attribute #33519

Description

@SebastianStamm

Description

BufferGeometry.computeVertexNormals checks if the normal attribute exists and initializes it with the size of the position attribute. If it already exists, it is reused and reset.

let normalAttribute = this.getAttribute( 'normal' );
if ( normalAttribute === undefined ) {
normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 );
this.setAttribute( 'normal', normalAttribute );
} else {
// reset existing normals to zero
for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) {
normalAttribute.setXYZ( i, 0, 0, 0 );
}
}

If you call computeVertexNormals, then change the size of the position attribute (e.g. to reflect changes to the model), and call computeVertexNormals again, the attribute retains its original size, either missing entries (if the position attribute has more entries), or having too many values (if the position attribute is now smaller).

I would expect that after calling computeVertexNormals, the normal attribute has the same size as the position attribute.

Workaround

Check if the position attribute has a different count than the normal attribute and delete the normal attribute before calling computeVertexNormals

Other affected methods

I assume BufferGeometry.computeTangents is also affected.

Reproduction steps

  1. Create a Buffer Geometry and set the positions attribute
  2. Call computeVertexNormals
  3. Overwrite the positions attribute with a new BufferAttribute of a different size
  4. Call computeVertexNormals again

Expected:
The normal attribute has the same size as the new position attribute.

Observed:
The normal attribute has the size of the old position attribute.

Code

import * as THREE from 'three';

const geometry = new THREE.BufferGeometry();

// create a simple triangle (3 vertices)
geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [
	-1.0, -1.0,  1.0, // v0
	 1.0, -1.0,  1.0, // v1
	 1.0,  1.0,  1.0, // v2
] ), 3 ) );

// Creates the `normal` attribute
geometry.computeVertexNormals();

let normalAttr = geometry.getAttribute( 'normal' );
console.log(normalAttr.count); // correctly displays count of 3

// overwrite the position attribute with new values (a square, 6 vertices)
geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [
	-1.0, -1.0,  1.0, // v0
	 1.0, -1.0,  1.0, // v1
	 1.0,  1.0,  1.0, // v2
	 1.0,  1.0,  1.0, // v3
	-1.0,  1.0,  1.0, // v4
	-1.0, -1.0,  1.0  // v5
] ), 3 ) );

// Reuses the `normal` attribute created in the first call
geometry.computeVertexNormals();

// should be the same, but just to be safe, retrieve the current normal attribute again
normalAttr = geometry.getAttribute( 'normal' );

console.log(normalAttr.count); // !!! still 3 - but should be 6 !!!

Live example

https://stackblitz.com/edit/typescript-1ofnthap?file=index.ts

Screenshots

No response

Version

0.184.0

Device

Desktop

Browser

Chrome

OS

Windows

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions