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

Mesh: Add volume method #27906

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 67 additions & 21 deletions build/three.cjs
Expand Up @@ -11491,6 +11491,59 @@ class Mesh extends Object3D {

}

volume( precision ) {

function calculateVolume( { geometry, precision } ) {

let volume = 0;
const vertices = geometry.attributes.position.array;
let indexes = geometry.index ? geometry.index.array : null;

function triangularVolume( p1, p2, p3 ) {

const v321 = p3.x * p2.y * p1.z;
const v231 = p2.x * p3.y * p1.z;
const v312 = p3.x * p1.y * p2.z;
const v132 = p2.x * p1.y * p3.z;
const v213 = p1.x * p3.y * p2.z;
const v123 = p1.x * p2.y * p3.z;
return ( 1.0 / 6.0 ) * ( - v321 + v231 + v312 - v132 - v213 + v123 );

}

if ( ! indexes ) {

indexes = Array.from( { length: vertices.length / 3 }, ( _, i ) => i );

}

for ( let i = 0; i < indexes.length; i += 3 ) {

const a = new THREE.Vector3().fromArray( vertices, indexes[ i ] * 3 );
const b = new THREE.Vector3().fromArray( vertices, indexes[ i + 1 ] * 3 );
const c = new THREE.Vector3().fromArray( vertices, indexes[ i + 2 ] * 3 );
volume += triangularVolume( a, b, c );

}

if ( precision ) {

return Math.abs( volume );

} else {

return Math.abs( volume / 1000 );

}

}

const vol = calculateVolume( { geometry: this.geometry, precision: precision } );

return vol;

}

copy( source, recursive ) {

super.copy( source, recursive );
Expand Down Expand Up @@ -21764,7 +21817,9 @@ function WebGLRenderState( extensions ) {
lightsArray: lightsArray,
shadowsArray: shadowsArray,

lights: lights
lights: lights,

transmissionRenderTarget: null
};

return {
Expand Down Expand Up @@ -28245,10 +28300,6 @@ class WebGLRenderer {
let _clippingEnabled = false;
let _localClippingEnabled = false;

// transmission

let _transmissionRenderTarget = null;

// camera matrices cache

const _projScreenMatrix = new Matrix4();
Expand Down Expand Up @@ -28698,13 +28749,6 @@ class WebGLRenderer {
xr.removeEventListener( 'sessionstart', onXRSessionStart );
xr.removeEventListener( 'sessionend', onXRSessionEnd );

if ( _transmissionRenderTarget ) {

_transmissionRenderTarget.dispose();
_transmissionRenderTarget = null;

}

animation.stop();

};
Expand Down Expand Up @@ -29456,9 +29500,9 @@ class WebGLRenderer {

}

if ( _transmissionRenderTarget === null ) {
if ( currentRenderState.state.transmissionRenderTarget === null ) {

_transmissionRenderTarget = new WebGLRenderTarget( 1, 1, {
currentRenderState.state.transmissionRenderTarget = new WebGLRenderTarget( 1, 1, {
generateMipmaps: true,
type: ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ) ? HalfFloatType : UnsignedByteType,
minFilter: LinearMipmapLinearFilter,
Expand All @@ -29477,13 +29521,15 @@ class WebGLRenderer {

}

const transmissionRenderTarget = currentRenderState.state.transmissionRenderTarget;

_this.getDrawingBufferSize( _vector2 );
_transmissionRenderTarget.setSize( _vector2.x, _vector2.y );
transmissionRenderTarget.setSize( _vector2.x, _vector2.y );

//

const currentRenderTarget = _this.getRenderTarget();
_this.setRenderTarget( _transmissionRenderTarget );
_this.setRenderTarget( transmissionRenderTarget );

_this.getClearColor( _currentClearColor );
_currentClearAlpha = _this.getClearAlpha();
Expand All @@ -29498,8 +29544,8 @@ class WebGLRenderer {

renderObjects( opaqueObjects, scene, camera );

textures.updateMultisampleRenderTarget( _transmissionRenderTarget );
textures.updateRenderTargetMipmap( _transmissionRenderTarget );
textures.updateMultisampleRenderTarget( transmissionRenderTarget );
textures.updateRenderTargetMipmap( transmissionRenderTarget );

let renderTargetNeedsUpdate = false;

Expand Down Expand Up @@ -29532,8 +29578,8 @@ class WebGLRenderer {

if ( renderTargetNeedsUpdate === true ) {

textures.updateMultisampleRenderTarget( _transmissionRenderTarget );
textures.updateRenderTargetMipmap( _transmissionRenderTarget );
textures.updateMultisampleRenderTarget( transmissionRenderTarget );
textures.updateRenderTargetMipmap( transmissionRenderTarget );

}

Expand Down Expand Up @@ -30066,7 +30112,7 @@ class WebGLRenderer {

}

materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget );
materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget );

WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures );

Expand Down
88 changes: 67 additions & 21 deletions build/three.module.js
Expand Up @@ -11489,6 +11489,59 @@ class Mesh extends Object3D {

}

volume( precision ) {

function calculateVolume( { geometry, precision } ) {

let volume = 0;
const vertices = geometry.attributes.position.array;
let indexes = geometry.index ? geometry.index.array : null;

function triangularVolume( p1, p2, p3 ) {

const v321 = p3.x * p2.y * p1.z;
const v231 = p2.x * p3.y * p1.z;
const v312 = p3.x * p1.y * p2.z;
const v132 = p2.x * p1.y * p3.z;
const v213 = p1.x * p3.y * p2.z;
const v123 = p1.x * p2.y * p3.z;
return ( 1.0 / 6.0 ) * ( - v321 + v231 + v312 - v132 - v213 + v123 );

}

if ( ! indexes ) {

indexes = Array.from( { length: vertices.length / 3 }, ( _, i ) => i );

}

for ( let i = 0; i < indexes.length; i += 3 ) {

const a = new THREE.Vector3().fromArray( vertices, indexes[ i ] * 3 );
const b = new THREE.Vector3().fromArray( vertices, indexes[ i + 1 ] * 3 );
const c = new THREE.Vector3().fromArray( vertices, indexes[ i + 2 ] * 3 );
volume += triangularVolume( a, b, c );

}

if ( precision ) {

return Math.abs( volume );

} else {

return Math.abs( volume / 1000 );

}

}

const vol = calculateVolume( { geometry: this.geometry, precision: precision } );

return vol;

}

copy( source, recursive ) {

super.copy( source, recursive );
Expand Down Expand Up @@ -21762,7 +21815,9 @@ function WebGLRenderState( extensions ) {
lightsArray: lightsArray,
shadowsArray: shadowsArray,

lights: lights
lights: lights,

transmissionRenderTarget: null
};

return {
Expand Down Expand Up @@ -28243,10 +28298,6 @@ class WebGLRenderer {
let _clippingEnabled = false;
let _localClippingEnabled = false;

// transmission

let _transmissionRenderTarget = null;

// camera matrices cache

const _projScreenMatrix = new Matrix4();
Expand Down Expand Up @@ -28696,13 +28747,6 @@ class WebGLRenderer {
xr.removeEventListener( 'sessionstart', onXRSessionStart );
xr.removeEventListener( 'sessionend', onXRSessionEnd );

if ( _transmissionRenderTarget ) {

_transmissionRenderTarget.dispose();
_transmissionRenderTarget = null;

}

animation.stop();

};
Expand Down Expand Up @@ -29454,9 +29498,9 @@ class WebGLRenderer {

}

if ( _transmissionRenderTarget === null ) {
if ( currentRenderState.state.transmissionRenderTarget === null ) {

_transmissionRenderTarget = new WebGLRenderTarget( 1, 1, {
currentRenderState.state.transmissionRenderTarget = new WebGLRenderTarget( 1, 1, {
generateMipmaps: true,
type: ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ) ? HalfFloatType : UnsignedByteType,
minFilter: LinearMipmapLinearFilter,
Expand All @@ -29475,13 +29519,15 @@ class WebGLRenderer {

}

const transmissionRenderTarget = currentRenderState.state.transmissionRenderTarget;

_this.getDrawingBufferSize( _vector2 );
_transmissionRenderTarget.setSize( _vector2.x, _vector2.y );
transmissionRenderTarget.setSize( _vector2.x, _vector2.y );

//

const currentRenderTarget = _this.getRenderTarget();
_this.setRenderTarget( _transmissionRenderTarget );
_this.setRenderTarget( transmissionRenderTarget );

_this.getClearColor( _currentClearColor );
_currentClearAlpha = _this.getClearAlpha();
Expand All @@ -29496,8 +29542,8 @@ class WebGLRenderer {

renderObjects( opaqueObjects, scene, camera );

textures.updateMultisampleRenderTarget( _transmissionRenderTarget );
textures.updateRenderTargetMipmap( _transmissionRenderTarget );
textures.updateMultisampleRenderTarget( transmissionRenderTarget );
textures.updateRenderTargetMipmap( transmissionRenderTarget );

let renderTargetNeedsUpdate = false;

Expand Down Expand Up @@ -29530,8 +29576,8 @@ class WebGLRenderer {

if ( renderTargetNeedsUpdate === true ) {

textures.updateMultisampleRenderTarget( _transmissionRenderTarget );
textures.updateRenderTargetMipmap( _transmissionRenderTarget );
textures.updateMultisampleRenderTarget( transmissionRenderTarget );
textures.updateRenderTargetMipmap( transmissionRenderTarget );

}

Expand Down Expand Up @@ -30064,7 +30110,7 @@ class WebGLRenderer {

}

materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget );
materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget );

WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures );

Expand Down
2 changes: 1 addition & 1 deletion build/three.module.min.js

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions src/objects/Mesh.js
Expand Up @@ -49,6 +49,59 @@ class Mesh extends Object3D {

}

volume( precision ) {
Copy link
Collaborator

@Mugen87 Mugen87 Mar 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about moving this routine into SceneUtils as SceneUtils.computeMeshVolume() instead?

To me, this method is too specific to be for the core.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its not appropiate since that would be the scene's volume an not the mesh's volume; I would add a scene method to get all individual volumes added.

Not sure how often it could be used though

Copy link
Collaborator

@Mugen87 Mugen87 Mar 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be a static method so you use it like so:

const volume = SceneUtils.computeMeshVolume( mesh );

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also do a MeshUtiks...


function calculateVolume( { geometry, precision } ) {

let volume = 0;
const vertices = geometry.attributes.position.array;
let indexes = geometry.index ? geometry.index.array : null;

function triangularVolume( p1, p2, p3 ) {

const v321 = p3.x * p2.y * p1.z;
Copy link
Collaborator

@Mugen87 Mugen87 Mar 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this computation is based on some kind of resource/reference. Do you mind sharing it here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isalgadof I'm not sure this is correct.... Perhaps you're referring to equation (5), which computes the volume of a tetrahedron? But to do this you would need to first compute the tetrahedral triangulation of the mesh, which I don't see here. The delaunay-triangulate npm package would do that part for you, or could be a good reference if you'd prefer to implement it manually.

In either case, it's likely this implementation will become more complex than can be added to the THREE.Mesh class... Either BufferGeometryUtils or SceneUtils would be good choices in my opinion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, never mind my comment above! I see the method. Tetrahedral triangulation can be skipped. I might suggest adding a comment on this method to explain that it is computing the signed volume of a tetrahedron, having the triangle as one face and the origin as the other.

const v231 = p2.x * p3.y * p1.z;
const v312 = p3.x * p1.y * p2.z;
const v132 = p2.x * p1.y * p3.z;
const v213 = p1.x * p3.y * p2.z;
const v123 = p1.x * p2.y * p3.z;
return ( 1.0 / 6.0 ) * ( - v321 + v231 + v312 - v132 - v213 + v123 );

}

if ( ! indexes ) {

indexes = Array.from( { length: vertices.length / 3 }, ( _, i ) => i );

}

for ( let i = 0; i < indexes.length; i += 3 ) {

const a = new THREE.Vector3().fromArray( vertices, indexes[ i ] * 3 );
isalgadof marked this conversation as resolved.
Show resolved Hide resolved
const b = new THREE.Vector3().fromArray( vertices, indexes[ i + 1 ] * 3 );
const c = new THREE.Vector3().fromArray( vertices, indexes[ i + 2 ] * 3 );
volume += triangularVolume( a, b, c );

}

if ( precision ) {

return Math.abs( volume );

} else {

return Math.abs( volume / 1000 );

}

}

const vol = calculateVolume( { geometry: this.geometry, precision: precision } );
isalgadof marked this conversation as resolved.
Show resolved Hide resolved

return vol;

}

copy( source, recursive ) {

super.copy( source, recursive );
Expand Down
5 changes: 5 additions & 0 deletions test/unit/src/objects/Mesh.tests.js
Expand Up @@ -91,6 +91,11 @@ export default QUnit.module( 'Objects', () => {

} );

QUnit.todo( 'volume', ( assert ) => {

assert.ok( false, 'everything\'s gonna be alright' );

} );
QUnit.todo( 'updateMorphTargets', ( assert ) => {

assert.ok( false, 'everything\'s gonna be alright' );
Expand Down