Repository: software-mansion/TypeGPU
Pull request #1317 (2025-05-30 11:59:14)

feat(typegpu/std): implement cosh, acosh, exp2
- cosh
- acosh
- exp2


Patch:
diff --git a/packages/typegpu/src/data/vectorOps.ts b/packages/typegpu/src/data/vectorOps.ts
index 6133bc94..5a3b5f4b 100644
--- a/packages/typegpu/src/data/vectorOps.ts
+++ b/packages/typegpu/src/data/vectorOps.ts
@@ -307,6 +307,17 @@ export const VectorOps = {
     vec4u: unary4u(Math.acos),
   } as Record<VecKind, <T extends vBase>(v: T) => T>,
 
+  acosh: {
+    vec2f: unary2f(Math.acosh),
+    vec2h: unary2h(Math.acosh),
+
+    vec3f: unary3f(Math.acosh),
+    vec3h: unary3h(Math.acosh),
+
+    vec4f: unary4f(Math.acosh),
+    vec4h: unary4h(Math.acosh),
+  } as Record<VecKind, <T extends vBase>(v: T) => T>,
+
   asin: {
     vec2f: unary2f(Math.asin),
     vec2h: unary2h(Math.asin),
@@ -1014,6 +1025,17 @@ export const VectorOps = {
     vec4h: unary4h(Math.cos),
   } as Record<VecKind, <T extends vBase>(v: T) => T>,
 
+  cosh: {
+    vec2f: unary2f(Math.cosh),
+    vec2h: unary2h(Math.cosh),
+
+    vec3f: unary3f(Math.cosh),
+    vec3h: unary3h(Math.cosh),
+
+    vec4f: unary4f(Math.cosh),
+    vec4h: unary4h(Math.cosh),
+  } as Record<VecKind, <T extends vBase>(v: T) => T>,
+
   exp: {
     vec2f: unary2f(Math.exp),
     vec2h: unary2h(Math.exp),
@@ -1025,6 +1047,17 @@ export const VectorOps = {
     vec4h: unary4h(Math.exp),
   } as Record<VecKind, <T extends vBase>(v: T) => T>,
 
+  exp2: {
+    vec2f: unary2f((val) => 2 ** val),
+    vec2h: unary2h((val) => 2 ** val),
+
+    vec3f: unary3f((val) => 2 ** val),
+    vec3h: unary3h((val) => 2 ** val),
+
+    vec4f: unary4f((val) => 2 ** val),
+    vec4h: unary4h((val) => 2 ** val),
+  } as Record<VecKind, <T extends vBase>(v: T) => T>,
+
   fract: {
     vec2f: unary2f((value) => value - Math.floor(value)),
     vec2h: unary2h((value) => value - Math.floor(value)),
diff --git a/packages/typegpu/src/std/index.ts b/packages/typegpu/src/std/index.ts
index aa93a0c3..12ae6062 100644
--- a/packages/typegpu/src/std/index.ts
+++ b/packages/typegpu/src/std/index.ts
@@ -10,15 +10,18 @@ export {
   // builtin functions
   abs,
   acos,
+  acosh,
   asin,
   atan2,
   ceil,
   clamp,
   cos,
+  cosh,
   cross,
   distance,
   dot,
   exp,
+  exp2,
   floor,
   fract,
   length,
diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts
index 20e7c0e2..7a42b089 100644
--- a/packages/typegpu/src/std/numeric.ts
+++ b/packages/typegpu/src/std/numeric.ts
@@ -268,23 +268,39 @@ export const acos = createDualImpl(
     if (typeof value === 'number') {
       return Math.acos(value) as T;
     }
-    return VectorOps.acos[(value as AnyFloatVecInstance).kind](
-      value as never,
-    ) as T;
+    return VectorOps.acos[value.kind](value) as T;
   },
   // GPU implementation
   (value) => snip(`acos(${value.value})`, value.dataType),
 );
 
+/**
+ * @privateRemarks
+ * https://www.w3.org/TR/WGSL/#acosh-builtin
+ */
+export const acosh = createDualImpl(
+  // CPU implementation
+  <T extends AnyFloatVecInstance | number>(value: T): T => {
+    if (typeof value === 'number') {
+      return Math.acosh(value) as T;
+    }
+    return VectorOps.acosh[value.kind](value) as T;
+  },
+  // GPU implementation
+  (value) => snip(`acosh(${value.value})`, value.dataType),
+);
+
+/**
+ * @privateRemarks
+ * https://www.w3.org/TR/WGSL/#asin-builtin
+ */
 export const asin = createDualImpl(
   // CPU implementation
   <T extends AnyFloatVecInstance | number>(value: T): T => {
     if (typeof value === 'number') {
       return Math.asin(value) as T;
     }
-    return VectorOps.asin[(value as AnyFloatVecInstance).kind](
-      value as never,
-    ) as T;
+    return VectorOps.asin[value.kind](value) as T;
   },
   // GPU implementation
   (value) => snip(`asin(${value.value})`, value.dataType),
@@ -343,6 +359,22 @@ export const cos = createDualImpl(
   (value) => snip(`cos(${value.value})`, value.dataType),
 );
 
+/**
+ * @privateRemarks
+ * https://www.w3.org/TR/WGSL/#cosh-builtin
+ */
+export const cosh = createDualImpl(
+  // CPU implementation
+  <T extends AnyFloatVecInstance | number>(value: T): T => {
+    if (typeof value === 'number') {
+      return Math.cosh(value) as T;
+    }
+    return VectorOps.cosh[value.kind](value) as T;
+  },
+  // GPU implementation
+  (value) => snip(`cosh(${value.value})`, value.dataType),
+);
+
 /**
  * @privateRemarks
  * https://www.w3.org/TR/WGSL/#cross-builtin
@@ -498,6 +530,22 @@ export const exp = createDualImpl(
   (value) => snip(`exp(${value.value})`, value.dataType),
 );
 
+/**
+ * @privateRemarks
+ * https://www.w3.org/TR/WGSL/#exp2-builtin
+ */
+export const exp2 = createDualImpl(
+  // CPU implementation
+  <T extends AnyFloatVecInstance | number>(value: T): T => {
+    if (typeof value === 'number') {
+      return (2 ** value) as T;
+    }
+    return VectorOps.exp2[value.kind](value) as T;
+  },
+  // GPU implementation
+  (value) => snip(`exp2(${value.value})`, value.dataType),
+);
+
 type PowOverload = {
   (base: number, exponent: number): number;
   <T extends AnyFloatVecInstance>(base: T, exponent: T): T;


Test patch:
diff --git a/packages/typegpu/tests/std/numeric/acosh.test.ts b/packages/typegpu/tests/std/numeric/acosh.test.ts
new file mode 100644
index 00000000..7a052fe8
--- /dev/null
+++ b/packages/typegpu/tests/std/numeric/acosh.test.ts
@@ -0,0 +1,35 @@
+import { describe, expect, it } from 'vitest';
+import { vec2f, vec3f, vec4f } from '../../../src/data/index.ts';
+import { acosh, isCloseTo } from '../../../src/std/index.ts';
+
+describe('acosh', () => {
+  it('computes acosh of a number', () => {
+    expect(acosh(1)).toBeCloseTo(0);
+    expect(acosh(Math.cosh(1))).toBeCloseTo(1);
+    expect(acosh(Math.cosh(-1))).toBeCloseTo(1);
+  });
+
+  it('computes acosh of vec2f', () => {
+    const input = vec2f(1, Math.cosh(1));
+    const expected = vec2f(Math.acosh(1), 1);
+    expect(isCloseTo(acosh(input), expected)).toBe(true);
+  });
+
+  it('tests acosh(cosh())', () => {
+    const input = vec2f(1, Math.cosh(1));
+    const expected = vec2f(Math.acosh(1), Math.acosh(Math.cosh(1)));
+    expect(isCloseTo(acosh(input), expected)).toBe(true);
+  });
+
+  it('computes acosh of vec3f', () => {
+    const input = vec3f(1, Math.cosh(1), Math.cosh(-1));
+    const expected = vec3f(Math.acosh(1), 1, 1);
+    expect(isCloseTo(acosh(input), expected)).toBe(true);
+  });
+
+  it('computes acosh of vec4f', () => {
+    const input = vec4f(1, Math.cosh(1), Math.cosh(-1), Math.cosh(2));
+    const expected = vec4f(Math.acosh(1), 1, 1, 2);
+    expect(isCloseTo(acosh(input), expected)).toBe(true);
+  });
+});
diff --git a/packages/typegpu/tests/std/numeric/cosh.test.ts b/packages/typegpu/tests/std/numeric/cosh.test.ts
new file mode 100644
index 00000000..dc10141a
--- /dev/null
+++ b/packages/typegpu/tests/std/numeric/cosh.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, it } from 'vitest';
+import { vec2f, vec3f, vec4f } from '../../../src/data/index.ts';
+import { cosh, isCloseTo } from '../../../src/std/index.ts';
+
+describe('cosh', () => {
+  it('computes cosh of a number', () => {
+    expect(cosh(0)).toBeCloseTo(1);
+    expect(cosh(1)).toBeCloseTo(Math.cosh(1));
+    expect(cosh(-1)).toBeCloseTo(Math.cosh(-1));
+  });
+
+  it('computes cosh of vec2f', () => {
+    const input = vec2f(0, 1);
+    const expected = vec2f(Math.cosh(0), Math.cosh(1));
+    expect(isCloseTo(cosh(input), expected)).toBe(true);
+  });
+
+  it('computes cosh of vec3f', () => {
+    const input = vec3f(0, 1, -1);
+    const expected = vec3f(Math.cosh(0), Math.cosh(1), Math.cosh(-1));
+    expect(isCloseTo(cosh(input), expected)).toBe(true);
+  });
+
+  it('computes cosh of vec4f', () => {
+    const input = vec4f(0, 1, -1, 2);
+    const expected = vec4f(
+      Math.cosh(0),
+      Math.cosh(1),
+      Math.cosh(-1),
+      Math.cosh(2),
+    );
+    expect(isCloseTo(cosh(input), expected)).toBe(true);
+  });
+});
diff --git a/packages/typegpu/tests/std/numeric/exp2.test.ts b/packages/typegpu/tests/std/numeric/exp2.test.ts
new file mode 100644
index 00000000..f92c95c9
--- /dev/null
+++ b/packages/typegpu/tests/std/numeric/exp2.test.ts
@@ -0,0 +1,66 @@
+import { describe, expect, it } from 'vitest';
+import {
+  vec2f,
+  vec2h,
+  vec3f,
+  vec3h,
+  vec4f,
+  vec4h,
+} from '../../../src/data/index.ts';
+import { isCloseTo } from '../../../src/std/index.ts';
+import { exp2 } from '../../../src/std/numeric.ts';
+
+describe('exp2', () => {
+  it('computes exp2 of a number', () => {
+    expect(exp2(0)).toBeCloseTo(1);
+    expect(exp2(1)).toBeCloseTo(2);
+    expect(exp2(2)).toBeCloseTo(4);
+    expect(exp2(-1)).toBeCloseTo(0.5);
+    expect(exp2(-2)).toBeCloseTo(0.25);
+  });
+
+  it('computes exp2 of vec2f', () => {
+    const input = vec2f(0, 1);
+    const expected = vec2f(2 ** 0, 2 ** 1);
+    expect(isCloseTo(exp2(input), expected)).toBe(true);
+  });
+
+  it('computes exp2 of vec3f', () => {
+    const input = vec3f(0, 1, -1);
+    const expected = vec3f(2 ** 0, 2 ** 1, 2 ** -1);
+    expect(isCloseTo(exp2(input), expected)).toBe(true);
+  });
+
+  it('computes exp2 of vec4f', () => {
+    const input = vec4f(0, 1, -1, 2);
+    const expected = vec4f(2 ** 0, 2 ** 1, 2 ** -1, 2 ** 2);
+    expect(isCloseTo(exp2(input), expected)).toBe(true);
+  });
+
+  it('computes exp2 of vec2h', () => {
+    const input = vec2h(0, 1);
+    const expected = vec2h(2 ** 0, 2 ** 1);
+    const result = exp2(input);
+    expect(result.x).toBeCloseTo(expected.x);
+    expect(result.y).toBeCloseTo(expected.y);
+  });
+
+  it('computes exp2 of vec3h', () => {
+    const input = vec3h(0, 1, -1);
+    const expected = vec3h(2 ** 0, 2 ** 1, 2 ** -1);
+    const result = exp2(input);
+    expect(result.x).toBeCloseTo(expected.x);
+    expect(result.y).toBeCloseTo(expected.y);
+    expect(result.z).toBeCloseTo(expected.z);
+  });
+
+  it('computes exp2 of vec4h', () => {
+    const input = vec4h(0, 1, -1, 2);
+    const expected = vec4h(2 ** 0, 2 ** 1, 2 ** -1, 2 ** 2);
+    const result = exp2(input);
+    expect(result.x).toBeCloseTo(expected.x);
+    expect(result.y).toBeCloseTo(expected.y);
+    expect(result.z).toBeCloseTo(expected.z);
+    expect(result.w).toBeCloseTo(expected.w);
+  });
+});