From 1bd39c1a06a2e1f9c9b89e870bd3404ff4f045b0 Mon Sep 17 00:00:00 2001 From: Hannah Watsky Date: Mon, 4 May 2026 21:58:56 -0400 Subject: [PATCH] fix: make smoothstep and step available as chained methods on vec nodes --- tsl-testing/node-type.test.ts | 124 ++++++++++++++++++++++- types/three/src/nodes/math/MathNode.d.ts | 53 +++++++++- 2 files changed, 172 insertions(+), 5 deletions(-) diff --git a/tsl-testing/node-type.test.ts b/tsl-testing/node-type.test.ts index 52fac8da3..c2ce82b6a 100644 --- a/tsl-testing/node-type.test.ts +++ b/tsl-testing/node-type.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest'; -import { Fn, vec2, vec3, vec4 } from 'three/tsl'; +import { Fn, smoothstep, smoothstepElement, step, stepElement, vec2, vec3, vec4 } from 'three/tsl'; import * as THREE from 'three/webgpu'; const renderer = new THREE.WebGPURenderer(); @@ -232,3 +232,125 @@ test('vec3(1, 0, 2).flipZX()', async () => { await renderer.init(); renderer.render(scene, camera); }); + +// smoothstep — chained method on vector nodes + +test('vec2(0.5, 0.5).smoothstep(0.1, 0.9)', async () => { + const result: THREE.Node<'vec2'> = vec2(0.5, 0.5).smoothstep(0.1, 0.9); + + expect(result.getNodeType(nodeBuilder)).toBe('vec2'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +test('vec3(0.5, 0.5, 0.5).smoothstep(float, float)', async () => { + const result: THREE.Node<'vec3'> = vec3(0.5, 0.5, 0.5).smoothstep(0.1, 0.9); + + expect(result.getNodeType(nodeBuilder)).toBe('vec3'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +test('vec4(0.5, 0.5, 0.5, 0.5).smoothstep(float, float)', async () => { + const result: THREE.Node<'vec4'> = vec4(0.5, 0.5, 0.5, 0.5).smoothstep(0.1, 0.9); + + expect(result.getNodeType(nodeBuilder)).toBe('vec4'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +// smoothstep — standalone function with vector args + +test('smoothstep(float, float, vec3)', async () => { + const result: THREE.Node<'vec3'> = smoothstep(0.1, 0.9, vec3(0.5, 0.5, 0.5)); + + expect(result.getNodeType(nodeBuilder)).toBe('vec3'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +// smoothstepElement — standalone function with vector args + +test('smoothstepElement(vec3, float, float)', async () => { + const result: THREE.Node<'vec3'> = smoothstepElement(vec3(0.5, 0.5, 0.5), 0.1, 0.9); + + expect(result.getNodeType(nodeBuilder)).toBe('vec3'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +// step — chained method on vector nodes + +test('vec2(0.5, 0.5).step(0.3)', async () => { + const result: THREE.Node<'vec2'> = vec2(0.5, 0.5).step(0.3); + + expect(result.getNodeType(nodeBuilder)).toBe('vec2'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +test('vec3(0.5, 0.5, 0.5).step(float)', async () => { + const result: THREE.Node<'vec3'> = vec3(0.5, 0.5, 0.5).step(0.3); + + expect(result.getNodeType(nodeBuilder)).toBe('vec3'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +test('vec4(0.5, 0.5, 0.5, 0.5).step(float)', async () => { + const result: THREE.Node<'vec4'> = vec4(0.5, 0.5, 0.5, 0.5).step(0.3); + + expect(result.getNodeType(nodeBuilder)).toBe('vec4'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +// step — standalone function with vector args + +test('step(float, vec3)', async () => { + const result: THREE.Node<'vec3'> = step(0.3, vec3(0.5, 0.5, 0.5)); + + expect(result.getNodeType(nodeBuilder)).toBe('vec3'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); + +// stepElement — standalone function with vector args + +test('stepElement(vec3, float)', async () => { + const result: THREE.Node<'vec3'> = stepElement(vec3(0.5, 0.5, 0.5), 0.3); + + expect(result.getNodeType(nodeBuilder)).toBe('vec3'); + + scene.backgroundNode = result.debug(); + + await renderer.init(); + renderer.render(scene, camera); +}); diff --git a/types/three/src/nodes/math/MathNode.d.ts b/types/three/src/nodes/math/MathNode.d.ts index 13f9f34e2..420ebf8f6 100644 --- a/types/three/src/nodes/math/MathNode.d.ts +++ b/types/three/src/nodes/math/MathNode.d.ts @@ -503,7 +503,13 @@ declare module "../core/Node.js" { } } -export const step: (x: FloatOrNumber, y: FloatOrNumber) => Node<"float">; +interface Step { + (x: FloatOrNumber, y: FloatOrNumber): Node<"float">; + (x: Vec2OrLessOrFloat, y: Vec2OrLessOrFloat): Node<"vec2">; + (x: Vec3OrLessOrFloat, y: Vec3OrLessOrFloat): Node<"vec3">; + (x: Vec4OrLessOrFloat, y: Vec4OrLessOrFloat): Node<"vec4">; +} +export const step: Step; interface Reflect { (I: Vec2OrLessOrFloat, N: Vec2OrLessOrFloat): Node<"vec2">; @@ -754,6 +760,9 @@ declare module "../core/Node.js" { interface Smoothstep { (low: FloatOrNumber, high: FloatOrNumber, x: FloatOrNumber): Node<"float">; + (low: Vec2OrLessOrFloat, high: Vec2OrLessOrFloat, x: Vec2OrLessOrFloat): Node<"vec2">; + (low: Vec3OrLessOrFloat, high: Vec3OrLessOrFloat, x: Vec3OrLessOrFloat): Node<"vec3">; + (low: Vec4OrLessOrFloat, high: Vec4OrLessOrFloat, x: Vec4OrLessOrFloat): Node<"vec4">; } export const smoothstep: Smoothstep; @@ -815,22 +824,58 @@ declare module "../core/Node.js" { interface SmoothstepElement { (x: FloatOrNumber, low: FloatOrNumber, high: FloatOrNumber): Node<"float">; + (x: Vec2OrLessOrFloat, low: Vec2OrLessOrFloat, high: Vec2OrLessOrFloat): Node<"vec2">; + (x: Vec3OrLessOrFloat, low: Vec3OrLessOrFloat, high: Vec3OrLessOrFloat): Node<"vec3">; + (x: Vec4OrLessOrFloat, low: Vec4OrLessOrFloat, high: Vec4OrLessOrFloat): Node<"vec4">; } export const smoothstepElement: SmoothstepElement; -interface SmoothstepExtension { +interface SmoothstepFloatExtension { (low: FloatOrNumber, high: FloatOrNumber): Node<"float">; } +interface SmoothstepVec2Extension { + (low: Vec2OrLessOrFloat, high: Vec2OrLessOrFloat): Node<"vec2">; +} +interface SmoothstepVec3Extension { + (low: Vec3OrLessOrFloat, high: Vec3OrLessOrFloat): Node<"vec3">; +} +interface SmoothstepVec4Extension { + (low: Vec4OrLessOrFloat, high: Vec4OrLessOrFloat): Node<"vec4">; +} declare module "../core/Node.js" { interface FloatExtensions { - smoothstep: SmoothstepExtension; + smoothstep: SmoothstepFloatExtension; + } + interface Vec2Extensions { + smoothstep: SmoothstepVec2Extension; + } + interface Vec3Extensions { + smoothstep: SmoothstepVec3Extension; + } + interface Vec4Extensions { + smoothstep: SmoothstepVec4Extension; } } -export const stepElement: (x: FloatOrNumber, edge: FloatOrNumber) => Node<"float">; +interface StepElement { + (x: FloatOrNumber, edge: FloatOrNumber): Node<"float">; + (x: Vec2OrLessOrFloat, edge: Vec2OrLessOrFloat): Node<"vec2">; + (x: Vec3OrLessOrFloat, edge: Vec3OrLessOrFloat): Node<"vec3">; + (x: Vec4OrLessOrFloat, edge: Vec4OrLessOrFloat): Node<"vec4">; +} +export const stepElement: StepElement; declare module "../core/Node.js" { interface FloatExtensions { step: (edge: FloatOrNumber) => Node<"float">; } + interface Vec2Extensions { + step: (edge: Vec2OrLessOrFloat) => Node<"vec2">; + } + interface Vec3Extensions { + step: (edge: Vec3OrLessOrFloat) => Node<"vec3">; + } + interface Vec4Extensions { + step: (edge: Vec4OrLessOrFloat) => Node<"vec4">; + } } // GLSL alias function