Skip to content
Merged
96 changes: 36 additions & 60 deletions editor/js/Animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,39 +264,17 @@ function Animation( editor ) {
let currentClip = null;
let currentRoot = null;

// Get all clips from scene animations
// Get all clips of the selected object
function getAnimationClips() {

const scene = editor.scene;
const object = editor.selected;
const clips = [];
const seen = new Set();

scene.traverse( function ( object ) {
if ( object !== null ) {

if ( object.animations && object.animations.length > 0 ) {
for ( const clip of object.animations ) {

for ( const clip of object.animations ) {

if ( ! seen.has( clip.uuid ) ) {

seen.add( clip.uuid );
clips.push( { clip: clip, root: object } );

}

}

}

} );

// Also check scene.animations directly
for ( const clip of scene.animations ) {

if ( ! seen.has( clip.uuid ) ) {

seen.add( clip.uuid );
clips.push( { clip: clip, root: scene } );
clips.push( { clip: clip, root: object } );

}

Expand Down Expand Up @@ -367,14 +345,6 @@ function Animation( editor ) {

clipRow.addEventListener( 'click', function () {

if ( editor.selected !== root ) {

signals.objectSelected.remove( selectDefaultClip );
editor.select( root );
signals.objectSelected.add( selectDefaultClip );

}

selectClip( clip, root );
update(); // Refresh to update highlighting

Expand Down Expand Up @@ -499,24 +469,11 @@ function Animation( editor ) {

function selectClip( clip, root ) {

// Stop current action
if ( currentAction ) {

currentAction.stop();

}
const toggle = ( currentClip === clip );

if ( currentClip === clip ) {
deselectClip();

// Unselect clip
currentAction = null;
currentClip = null;
currentRoot = null;

timeText.setValue( '0.00' );
durationText.setValue( '0.00' );

} else {
if ( toggle === false ) {

// Select clip without playing
currentClip = clip;
Expand All @@ -530,6 +487,26 @@ function Animation( editor ) {

}

function deselectClip() {

// Stop current action
if ( currentAction ) {

currentAction.stop();

}

currentAction = null;
currentClip = null;
currentRoot = null;

timeText.setValue( '0.00' );
durationText.setValue( '0.00' );

playhead.style.left = labelWidth + 'px';

}

function showPath( clip, object ) {

hidePath();
Expand Down Expand Up @@ -557,11 +534,7 @@ function Animation( editor ) {

hidePath();
trackListContainer.innerHTML = '';
currentAction = null;
currentClip = null;
currentRoot = null;
timeText.setValue( '0.00' );
durationText.setValue( '0.00' );
deselectClip();

}

Expand All @@ -585,21 +558,24 @@ function Animation( editor ) {

}

function selectDefaultClip( object ) {
function onObjectSelected( object ) {

deselectClip();

if ( object !== null && object.animations && object.animations.length > 0 ) {
if ( object !== null && object.animations.length > 0 ) {

selectClip( object.animations[ 0 ], object );
update();

}

update();

}

updateTime();

// Auto-select clip when an object with animations is selected
signals.objectSelected.add( selectDefaultClip );
signals.objectSelected.add( onObjectSelected );

// Update when scene changes
signals.editorCleared.add( clear );
Expand Down
2 changes: 2 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export default [
'**/node_modules/**',
'**/build/**',
'examples/jsm/libs/**',
'test/treeshake/*.bundle.js',
'test/treeshake/*.bundle.min.js',
'editor/js/libs/acorn/**',
'editor/js/libs/codemirror/**',
'editor/js/libs/tern-threejs/**',
Expand Down
Binary file modified examples/screenshots/webgpu_layers.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/nodes/math/MathNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class MathNode extends TempNode {

const method = this.method;

if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT ) {
if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT || method === MathNode.DETERMINANT ) {

return 'float';

Expand Down
19 changes: 15 additions & 4 deletions src/nodes/math/MathUtils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { sub, mul, div, add } from './OperatorNode.js';
import { PI, pow, sin } from './MathNode.js';
import { PI, pow, sin, abs } from './MathNode.js';
import { select } from './ConditionalNode.js';

/**
* A function that remaps the `[0,1]` interval into the `[0,1]` interval.
Expand All @@ -25,7 +26,11 @@ export const parabola = ( x, k ) => pow( mul( 4.0, x.mul( sub( 1.0, x ) ) ), k )
* @param {Node<float>} k - `k=1` is the identity curve,`k<1` produces the classic `gain()` shape, and `k>1` produces "s" shaped curves.
* @return {Node<float>} The remapped value.
*/
export const gain = ( x, k ) => x.lessThan( 0.5 ) ? parabola( x.mul( 2.0 ), k ).div( 2.0 ) : sub( 1.0, parabola( mul( sub( 1.0, x ), 2.0 ), k ).div( 2.0 ) );
export const gain = ( x, k ) => select(
x.lessThan( 0.5 ),
pow( mul( 2.0, x ), k ).mul( 0.5 ),
sub( 1.0, pow( mul( 2.0, sub( 1.0, x ) ), k ).mul( 0.5 ) )
);

/**
* A function that remaps the `[0,1]` interval into the `[0,1]` interval.
Expand All @@ -39,7 +44,7 @@ export const gain = ( x, k ) => x.lessThan( 0.5 ) ? parabola( x.mul( 2.0 ), k ).
* @param {Node<float>} b - Second control parameter.
* @return {Node<float>} The remapped value.
*/
export const pcurve = ( x, a, b ) => pow( div( pow( x, a ), add( pow( x, a ), pow( sub( 1.0, x ), b ) ) ), 1.0 / a );
export const pcurve = ( x, a, b ) => pow( div( pow( x, a ), add( pow( x, a ), pow( sub( 1.0, x ), b ) ) ), div( 1.0, a ) );

/**
* A phase shifted sinus curve that starts at zero and ends at zero, with bouncing behavior.
Expand All @@ -51,4 +56,10 @@ export const pcurve = ( x, a, b ) => pow( div( pow( x, a ), add( pow( x, a ), po
* @param {Node<float>} k - Controls the amount of bounces.
* @return {Node<float>} The result value.
*/
export const sinc = ( x, k ) => sin( PI.mul( k.mul( x ).sub( 1.0 ) ) ).div( PI.mul( k.mul( x ).sub( 1.0 ) ) );
export const sinc = ( x, k ) => {

const arg = abs( PI.mul( k.mul( x ).sub( 1.0 ) ) ).max( 1e-6 ).toConst();

return sin( arg ).div( arg );

};
6 changes: 3 additions & 3 deletions src/nodes/utils/RotateNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ class RotateNode extends TempNode {
} else {

const rotation = rotationNode;
const rotationXMatrix = mat4( vec4( 1.0, 0.0, 0.0, 0.0 ), vec4( 0.0, cos( rotation.x ), sin( rotation.x ).negate(), 0.0 ), vec4( 0.0, sin( rotation.x ), cos( rotation.x ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
const rotationYMatrix = mat4( vec4( cos( rotation.y ), 0.0, sin( rotation.y ), 0.0 ), vec4( 0.0, 1.0, 0.0, 0.0 ), vec4( sin( rotation.y ).negate(), 0.0, cos( rotation.y ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
const rotationZMatrix = mat4( vec4( cos( rotation.z ), sin( rotation.z ).negate(), 0.0, 0.0 ), vec4( sin( rotation.z ), cos( rotation.z ), 0.0, 0.0 ), vec4( 0.0, 0.0, 1.0, 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
const rotationXMatrix = mat4( vec4( 1.0, 0.0, 0.0, 0.0 ), vec4( 0.0, cos( rotation.x ), sin( rotation.x ), 0.0 ), vec4( 0.0, sin( rotation.x ).negate(), cos( rotation.x ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
const rotationYMatrix = mat4( vec4( cos( rotation.y ), 0.0, sin( rotation.y ).negate(), 0.0 ), vec4( 0.0, 1.0, 0.0, 0.0 ), vec4( sin( rotation.y ), 0.0, cos( rotation.y ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );
const rotationZMatrix = mat4( vec4( cos( rotation.z ), sin( rotation.z ), 0.0, 0.0 ), vec4( sin( rotation.z ).negate(), cos( rotation.z ), 0.0, 0.0 ), vec4( 0.0, 0.0, 1.0, 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) );

return rotationXMatrix.mul( rotationYMatrix ).mul( rotationZMatrix ).mul( vec4( positionNode, 1.0 ) ).xyz;

Expand Down
1 change: 1 addition & 0 deletions src/renderers/webgpu/nodes/WGSLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ const wgslMethods = {
inverse_mat3: 'tsl_inverse_mat3',
inverse_mat4: 'tsl_inverse_mat4',
inversesqrt: 'inverseSqrt',
faceforward: 'faceForward',
bitcast: 'bitcast<f32>',
floatpack_snorm_2x16: 'pack2x16snorm',
floatpack_unorm_2x16: 'pack2x16unorm',
Expand Down
3 changes: 1 addition & 2 deletions test/unit/addons/tsl/GPUTest.tests.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { hash, vec3, float } from 'three/tsl';
import { sRGBTransferEOTF, sRGBTransferOETF } from 'three/tsl';
import { hash, vec3, float, sRGBTransferEOTF, sRGBTransferOETF } from 'three/tsl';
import { gpuTest, gpuFuzzTest } from './gpu-test-utils.js';

export default QUnit.module( 'TSL', () => {
Expand Down
32 changes: 32 additions & 0 deletions test/unit/addons/tsl/TSLDeterminant.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
float, mat3, mat4, determinant
} from 'three/tsl';
import { gpuTest } from './gpu-test-utils.js';

export default QUnit.module( 'TSL', () => {

QUnit.module( 'determinant()', () => {

gpuTest( 'determinant() of identity matrices is 1', ( { assert } ) => {

const I3 = mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 );
assert.closeAbs( determinant( I3 ), float( 1 ), 1e-5, 'determinant(I3) == 1' );

const I4 = mat4( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 );
assert.closeAbs( determinant( I4 ), float( 1 ), 1e-5, 'determinant(I4) == 1' );

} );

gpuTest( 'mat3 determinant of a diagonal scale matrix', ( { assert } ) => {

// determinant of a diagonal matrix is just the product of its
// diagonal entries -- 2 * 3 * 4 == 24, independent of the general
// cofactor-expansion implementation under test.
const scale = mat3( 2, 0, 0, 0, 3, 0, 0, 0, 4 );
assert.closeAbs( determinant( scale ), float( 24 ), 1e-4, 'determinant(diag(2,3,4)) == 24' );

} );

} );

} );
24 changes: 24 additions & 0 deletions test/unit/addons/tsl/TSLFaceForward.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
vec3, faceForward
} from 'three/tsl';
import { gpuTest } from './gpu-test-utils.js';

export default QUnit.module( 'TSL', () => {

QUnit.module( 'faceForward()', () => {

gpuTest( 'faceForward() picks the side facing the incident ray', ( { assert } ) => {

const n = vec3( 0, 1, 0 );

// dot(Nref, I) < 0 -> returns N unchanged.
assert.closeAbs( faceForward( n, vec3( 0, - 1, 0 ), vec3( 0, 1, 0 ) ), n, 1e-5, 'faceForward keeps N when Nref and I already face opposite ways' );

// dot(Nref, I) >= 0 -> returns -N.
assert.closeAbs( faceForward( n, vec3( 0, 1, 0 ), vec3( 0, 1, 0 ) ), n.negate(), 1e-5, 'faceForward flips N when Nref and I face the same way' );

} );

} );

} );
38 changes: 38 additions & 0 deletions test/unit/addons/tsl/TSLGainPcurve.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { float, gain, pcurve } from 'three/tsl';
import { gpuTest } from './gpu-test-utils.js';

export default QUnit.module( 'TSL', () => {

QUnit.module( 'gain() and pcurve()', () => {

gpuTest( 'gain() keeps the endpoints and midpoint fixed, k=1 is the identity', ( { assert } ) => {

// gain()'s doc comment: "k=1 is the identity curve" -- checked at
// several points, not just the fixed ones.
assert.closeAbs( gain( float( 0.2 ), float( 1 ) ), float( 0.2 ), 1e-4, 'gain(0.2, k=1) == 0.2 (identity)' );
assert.closeAbs( gain( float( 0.7 ), float( 1 ) ), float( 0.7 ), 1e-4, 'gain(0.7, k=1) == 0.7 (identity)' );

// Regardless of k, gain() fixes 0, 0.5 and 1 (a documented property
// of this remap shape).
assert.closeAbs( gain( float( 0 ), float( 3 ) ), float( 0 ), 1e-5, 'gain(0, k) == 0 for any k' );
assert.closeAbs( gain( float( 1 ), float( 3 ) ), float( 1 ), 1e-5, 'gain(1, k) == 1 for any k' );
assert.closeAbs( gain( float( 0.5 ), float( 3 ) ), float( 0.5 ), 1e-4, 'gain(0.5, k) == 0.5 for any k' );

} );

gpuTest( 'pcurve() matches its own documented formula and vanishes at the domain edges', ( { assert } ) => {

// pcurve(x, a, b) == (x^a / (x^a + (1-x)^b))^(1/a) -- from the
// function's own doc comment, computed independently in JS.
const x = 0.3, a = 2, b = 3;
const expected = Math.pow( Math.pow( x, a ) / ( Math.pow( x, a ) + Math.pow( 1 - x, b ) ), 1 / a );
assert.closeAbs( pcurve( float( x ), float( a ), float( b ) ), float( expected ), 1e-4, 'pcurve(0.3, 2, 3) matches the hand-computed formula' );

assert.closeAbs( pcurve( float( 0 ), float( 2 ), float( 3 ) ), float( 0 ), 1e-4, 'pcurve(0, a, b) == 0' );
assert.closeAbs( pcurve( float( 1 ), float( 2 ), float( 3 ) ), float( 1 ), 1e-4, 'pcurve(1, a, b) == 1' );

} );

} );

} );
Loading