diff --git a/packages/typegpu/src/data/numeric.ts b/packages/typegpu/src/data/numeric.ts index 2d29ef953..7e3c55b41 100644 --- a/packages/typegpu/src/data/numeric.ts +++ b/packages/typegpu/src/data/numeric.ts @@ -1,6 +1,6 @@ -import bin from 'typed-binary'; import { createDualImpl } from '../shared/generators.ts'; import { $internal } from '../shared/symbols.ts'; +import { snip } from './dataTypes.ts'; import type { AbstractFloat, AbstractInt, @@ -10,7 +10,6 @@ import type { I32, U32, } from './wgslTypes.ts'; -import { snip } from './dataTypes.ts'; export const abstractInt = { [$internal]: true, @@ -22,39 +21,61 @@ export const abstractFloat = { type: 'abstractFloat', } as AbstractFloat; +const boolCast = createDualImpl( + // CPU implementation + (v?: number | boolean) => { + if (v === undefined) { + return false; + } + if (typeof v === 'boolean') { + return v; + } + return !!v; + }, + // GPU implementation + (v) => snip(`bool(${v?.value ?? ''})`, bool), + 'boolCast', +); + /** * A schema that represents a boolean value. (equivalent to `bool` in WGSL) + * + * @example + * const value = bool(); // false + * @example + * const value = bool(0); // false + * @example + * const value = bool(-0); // false + * @example + * const value = bool(21.37); // true */ -export const bool: Bool = { - [$internal]: true, +export const bool: Bool = Object.assign(boolCast, { type: 'bool', -} as Bool; +}) as unknown as Bool; const u32Cast = createDualImpl( // CPU implementation - (v: number | boolean) => { + (v?: number | boolean) => { + if (v === undefined) { + return 0; + } if (typeof v === 'boolean') { return v ? 1 : 0; } - if (Number.isInteger(v)) { - if (v < 0 || v > 0xffffffff) { - console.warn(`u32 value ${v} overflowed`); - } - const value = v & 0xffffffff; - return value >>> 0; - } - return Math.max(0, Math.min(0xffffffff, Math.floor(v))); + return (v & 0xffffffff) >>> 0; }, // GPU implementation - (v) => snip(`u32(${v.value})`, u32), + (v) => snip(`u32(${v?.value ?? ''})`, u32), 'u32Cast', ); /** * A schema that represents an unsigned 32-bit integer value. (equivalent to `u32` in WGSL) * - * Can also be called to cast a value to an u32 in accordance with WGSL casting rules. - * + * @example + * const value = u32(); // 0 + * @example + * const value = u32(7); // 7 * @example * const value = u32(3.14); // 3 * @example @@ -68,33 +89,25 @@ export const u32: U32 = Object.assign(u32Cast, { const i32Cast = createDualImpl( // CPU implementation - (v: number | boolean) => { + (v?: number | boolean) => { + if (v === undefined) { + return 0; + } if (typeof v === 'boolean') { return v ? 1 : 0; } - if (Number.isInteger(v)) { - if (v < -0x80000000 || v > 0x7fffffff) { - console.warn(`i32 value ${v} overflowed`); - } - const value = v | 0; - return value & 0xffffffff; - } - // round towards zero - const value = v < 0 ? Math.ceil(v) : Math.floor(v); - return Math.max(-0x80000000, Math.min(0x7fffffff, value)); + return v | 0; }, // GPU implementation - (v) => { - return snip(`i32(${v.value})`, i32); - }, + (v) => snip(`i32(${v?.value ?? ''})`, i32), 'i32Cast', ); /** * A schema that represents a signed 32-bit integer value. (equivalent to `i32` in WGSL) * - * Can also be called to cast a value to an i32 in accordance with WGSL casting rules. - * + * @example + * const value = i32(); // 0 * @example * const value = i32(3.14); // 3 * @example @@ -108,24 +121,27 @@ export const i32: I32 = Object.assign(i32Cast, { const f32Cast = createDualImpl( // CPU implementation - (v: number | boolean) => { + (v?: number | boolean) => { + if (v === undefined) { + return 0; + } if (typeof v === 'boolean') { return v ? 1 : 0; } - const arr = new Float32Array(1); - arr[0] = v; - return arr[0]; + return v; }, // GPU implementation - (v) => snip(`f32(${v.value})`, f32), + (v) => snip(`f32(${v?.value ?? ''})`, f32), 'f32Cast', ); /** * A schema that represents a 32-bit float value. (equivalent to `f32` in WGSL) * - * Can also be called to cast a value to an f32. - * + * @example + * const value = f32(); // 0 + * @example + * const value = f32(1.23); // 1.23 * @example * const value = f32(true); // 1 */ @@ -135,29 +151,32 @@ export const f32: F32 = Object.assign(f32Cast, { const f16Cast = createDualImpl( // CPU implementation - (v: number | boolean) => { + (v?: number | boolean) => { + if (v === undefined) { + return 0; + } if (typeof v === 'boolean') { return v ? 1 : 0; } - const arr = new ArrayBuffer(2); - bin.f16.write(new bin.BufferWriter(arr), v); - return bin.f16.read(new bin.BufferReader(arr)); + return v; }, // GPU implementation // TODO: make usage of f16() in GPU mode check for feature availability and throw if not available - (v) => snip(`f16(${v.value})`, f16), + (v) => snip(`f16(${v?.value ?? ''})`, f16), 'f16Cast', ); /** * A schema that represents a 16-bit float value. (equivalent to `f16` in WGSL) * - * Can also be called to cast a value to an f16. - * + * @example + * const value = f16(); // 0 + * @example + * const value = f32(1.23); // 1.23 * @example * const value = f16(true); // 1 * @example - * const value = f16(21877.5); // 21872 + * const value = f16(21877.5); // 21877.5 */ export const f16: F16 = Object.assign(f16Cast, { type: 'f16', diff --git a/packages/typegpu/src/data/vectorImpl.ts b/packages/typegpu/src/data/vectorImpl.ts index e888ff66a..d17df8f12 100644 --- a/packages/typegpu/src/data/vectorImpl.ts +++ b/packages/typegpu/src/data/vectorImpl.ts @@ -1,11 +1,14 @@ import { $internal } from '../shared/symbols.ts'; import type { SelfResolvable } from '../types.ts'; +import { bool, f16, f32, i32, u32 } from './numeric.ts'; import type { VecKind } from './wgslTypes.ts'; // deno-fmt-ignore export abstract class VecBase extends Array implements SelfResolvable { public readonly [$internal] = true; abstract get kind(): VecKind; + abstract getElementSchema(): (v?: S) => S; + abstract get _Vec2(): new ( x: S, @@ -375,15 +378,30 @@ type Tuple4 = [S, S, S, S]; abstract class Vec2 extends VecBase implements Tuple2 { declare readonly length = 2; - abstract getDefaultValue(): S; - 0: S; - 1: S; + e0: S; + e1: S; - constructor(x?: S, y?: S) { + constructor(x?: S, y?: S, z?: S) { super(2); - this[0] = x ?? this.getDefaultValue(); - this[1] = y ?? x ?? this.getDefaultValue(); + this.e0 = this.getElementSchema()(x); + this.e1 = this.getElementSchema()(y ?? x); + } + + get [0]() { + return this.e0; + } + + get [1]() { + return this.e1; + } + + set [0](value: S) { + this.e0 = this.getElementSchema()(value); + } + + set [1](value: S) { + this.e1 = this.getElementSchema()(value); } get x() { @@ -395,27 +413,50 @@ abstract class Vec2 extends VecBase implements Tuple2 { } set x(value: S) { - this[0] = value; + this[0] = this.getElementSchema()(value); } set y(value: S) { - this[1] = value; + this[1] = this.getElementSchema()(value); } } abstract class Vec3 extends VecBase implements Tuple3 { declare readonly length = 3; - abstract getDefaultValue(): S; - 0: S; - 1: S; - 2: S; + e0: S; + e1: S; + e2: S; constructor(x?: S, y?: S, z?: S) { super(3); - this[0] = x ?? this.getDefaultValue(); - this[1] = y ?? x ?? this.getDefaultValue(); - this[2] = z ?? x ?? this.getDefaultValue(); + this.e0 = this.getElementSchema()(x); + this.e1 = this.getElementSchema()(y ?? x); + this.e2 = this.getElementSchema()(z ?? x); + } + + get [0]() { + return this.e0; + } + + get [1]() { + return this.e1; + } + + get [2]() { + return this.e2; + } + + set [0](value: S) { + this.e0 = this.getElementSchema()(value); + } + + set [1](value: S) { + this.e1 = this.getElementSchema()(value); + } + + set [2](value: S) { + this.e2 = this.getElementSchema()(value); } get x() { @@ -431,33 +472,64 @@ abstract class Vec3 extends VecBase implements Tuple3 { } set x(value: S) { - this[0] = value; + this[0] = this.getElementSchema()(value); } set y(value: S) { - this[1] = value; + this[1] = this.getElementSchema()(value); } set z(value: S) { - this[2] = value; + this[2] = this.getElementSchema()(value); } } abstract class Vec4 extends VecBase implements Tuple4 { declare readonly length = 4; - abstract getDefaultValue(): S; - 0: S; - 1: S; - 2: S; - 3: S; + e0: S; + e1: S; + e2: S; + e3: S; constructor(x?: S, y?: S, z?: S, w?: S) { super(4); - this[0] = x ?? this.getDefaultValue(); - this[1] = y ?? x ?? this.getDefaultValue(); - this[2] = z ?? x ?? this.getDefaultValue(); - this[3] = w ?? x ?? this.getDefaultValue(); + this.e0 = this.getElementSchema()(x); + this.e1 = this.getElementSchema()(y ?? x); + this.e2 = this.getElementSchema()(z ?? x); + this.e3 = this.getElementSchema()(w ?? x); + } + + get [0]() { + return this.e0; + } + + get [1]() { + return this.e1; + } + + get [2]() { + return this.e2; + } + + get [3]() { + return this.e3; + } + + set [0](value: S) { + this.e0 = this.getElementSchema()(value); + } + + set [1](value: S) { + this.e1 = this.getElementSchema()(value); + } + + set [2](value: S) { + this.e2 = this.getElementSchema()(value); + } + + set [3](value: S) { + this.e3 = this.getElementSchema()(value); } get x() { @@ -494,10 +566,9 @@ abstract class Vec4 extends VecBase implements Tuple4 { } export class Vec2fImpl extends Vec2 { - getDefaultValue() { - return 0; + getElementSchema() { + return f32; } - get kind() { return 'vec2f' as const; } @@ -514,10 +585,9 @@ export class Vec2fImpl extends Vec2 { } export class Vec2hImpl extends Vec2 { - getDefaultValue() { - return 0; + getElementSchema() { + return f16; } - get kind() { return 'vec2h' as const; } @@ -534,10 +604,9 @@ export class Vec2hImpl extends Vec2 { } export class Vec2iImpl extends Vec2 { - getDefaultValue() { - return 0; + getElementSchema() { + return i32; } - get kind() { return 'vec2i' as const; } @@ -554,10 +623,9 @@ export class Vec2iImpl extends Vec2 { } export class Vec2uImpl extends Vec2 { - getDefaultValue() { - return 0; + getElementSchema() { + return u32; } - get kind() { return 'vec2u' as const; } @@ -574,10 +642,9 @@ export class Vec2uImpl extends Vec2 { } export class Vec2bImpl extends Vec2 { - getDefaultValue() { - return false; + getElementSchema() { + return bool; } - get kind() { return 'vec2' as const; } @@ -594,10 +661,9 @@ export class Vec2bImpl extends Vec2 { } export class Vec3fImpl extends Vec3 { - getDefaultValue() { - return 0; + getElementSchema() { + return f32; } - get kind() { return 'vec3f' as const; } @@ -614,10 +680,9 @@ export class Vec3fImpl extends Vec3 { } export class Vec3hImpl extends Vec3 { - getDefaultValue() { - return 0; + getElementSchema() { + return f16; } - get kind() { return 'vec3h' as const; } @@ -634,10 +699,9 @@ export class Vec3hImpl extends Vec3 { } export class Vec3iImpl extends Vec3 { - getDefaultValue() { - return 0; + getElementSchema() { + return i32; } - get kind() { return 'vec3i' as const; } @@ -654,10 +718,9 @@ export class Vec3iImpl extends Vec3 { } export class Vec3uImpl extends Vec3 { - getDefaultValue() { - return 0; + getElementSchema() { + return u32; } - get kind() { return 'vec3u' as const; } @@ -674,10 +737,9 @@ export class Vec3uImpl extends Vec3 { } export class Vec3bImpl extends Vec3 { - getDefaultValue() { - return false; + getElementSchema() { + return bool; } - get kind() { return 'vec3' as const; } @@ -694,10 +756,9 @@ export class Vec3bImpl extends Vec3 { } export class Vec4fImpl extends Vec4 { - getDefaultValue() { - return 0; + getElementSchema() { + return f32; } - get kind() { return 'vec4f' as const; } @@ -714,10 +775,9 @@ export class Vec4fImpl extends Vec4 { } export class Vec4hImpl extends Vec4 { - getDefaultValue() { - return 0; + getElementSchema() { + return f16; } - get kind() { return 'vec4h' as const; } @@ -734,10 +794,9 @@ export class Vec4hImpl extends Vec4 { } export class Vec4iImpl extends Vec4 { - getDefaultValue() { - return 0; + getElementSchema() { + return i32; } - get kind() { return 'vec4i' as const; } @@ -754,10 +813,9 @@ export class Vec4iImpl extends Vec4 { } export class Vec4uImpl extends Vec4 { - getDefaultValue() { - return 0; + getElementSchema() { + return u32; } - get kind() { return 'vec4u' as const; } @@ -774,10 +832,9 @@ export class Vec4uImpl extends Vec4 { } export class Vec4bImpl extends Vec4 { - getDefaultValue() { - return false; + getElementSchema() { + return bool; } - get kind() { return 'vec4' as const; } diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index b0b6b5742..77fce3679 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -698,6 +698,8 @@ export interface Bool { readonly [$internal]: true; readonly type: 'bool'; readonly [$repr]: boolean; + + (v?: number | boolean): boolean; } /** @@ -708,7 +710,7 @@ export interface F32 { readonly type: 'f32'; readonly [$repr]: number; - (v: number | boolean): number; + (v?: number | boolean): number; } /** @@ -719,7 +721,7 @@ export interface F16 { readonly type: 'f16'; readonly [$repr]: number; - (v: number | boolean): number; + (v?: number | boolean): number; } /** @@ -731,7 +733,7 @@ export interface I32 { readonly [$repr]: number; readonly '~memIdent': I32 | Atomic | DecoratedLocation; - (v: number | boolean): number; + (v?: number | boolean): number; } /** @@ -743,7 +745,7 @@ export interface U32 { readonly [$repr]: number; readonly '~memIdent': U32 | Atomic | DecoratedLocation; - (v: number | boolean): number; + (v?: number | boolean): number; } /** diff --git a/packages/typegpu/tests/numeric.test.ts b/packages/typegpu/tests/numeric.test.ts index 7d6f73293..7cefd8f69 100644 --- a/packages/typegpu/tests/numeric.test.ts +++ b/packages/typegpu/tests/numeric.test.ts @@ -1,5 +1,7 @@ -import { describe, it } from 'vitest'; +import { describe, expect, it } from 'vitest'; import * as d from '../src/data/index.ts'; +import { tgpu } from '../src/index.ts'; +import { parse, parseResolved } from './utils/parseResolved.ts'; describe('f32', () => { it('differs in type from other numeric schemas', () => { @@ -48,3 +50,37 @@ describe('f16', () => { acceptsF16Schema(d.u32); }); }); + +it('has correct default values', () => { + expect(d.f32()).toBe(0); + expect(d.f16()).toBe(0); + expect(d.i32()).toBe(0); + expect(d.u32()).toBe(0); + expect(d.bool()).toBe(false); +}); + +describe('TGSL', () => { + it('works for default constructors', () => { + const main = tgpu['~unstable'] + .fn([])(() => { + const f = d.f32(); + const h = d.f16(); + const i = d.i32(); + const u = d.u32(); + const b = d.bool(); + }) + .$name('main'); + + expect(parseResolved({ main })).toBe( + parse(` + fn main() { + var f = f32(); + var h = f16(); + var i = i32(); + var u = u32(); + var b = bool(); + } + `), + ); + }); +}); diff --git a/packages/typegpu/tests/primitiveCast.test.ts b/packages/typegpu/tests/primitiveCast.test.ts index 517f9c22d..0d28314c8 100644 --- a/packages/typegpu/tests/primitiveCast.test.ts +++ b/packages/typegpu/tests/primitiveCast.test.ts @@ -1,12 +1,12 @@ import { describe, expect, it } from 'vitest'; -import { f16, f32, i32, u32 } from '../src/data/index.ts'; +import { bool, f16, f32, i32, u32 } from '../src/data/index.ts'; describe('u32', () => { it('casts a number to u32', () => { expect(u32(10)).toBe(10); expect(u32(10.5)).toBe(10); expect(u32(-10)).toBe(4294967286); - expect(u32(-10.5)).toBe(0); + expect(u32(-10.5)).toBe(4294967286); expect(u32(4294967296)).toBe(0); expect(u32(4294967297)).toBe(1); }); @@ -24,7 +24,6 @@ describe('i32', () => { expect(i32(-10)).toBe(-10); expect(i32(-10.5)).toBe(-10); expect(i32(4294967286)).toBe(-10); - expect(i32(4294967286.5)).toBe(2147483647); }); it('casts a boolean to i32', () => { @@ -40,9 +39,6 @@ describe('f32', () => { expect(f32(-10)).toBe(-10); expect(f32(-10.5)).toBe(-10.5); expect(f32(4294967296)).toBe(4294967296); - expect(f32(4294967297)).toBe(4294967296); // 4294967297 is not exactly representable as an f32 - expect(f32(4294967297.5)).toBe(4294967296); // 4294967297.5 is not exactly representable as an f32 - expect(f32(4295029249)).toBe(4295029248); // 4295029249 is not exactly representable as an f32 }); it('casts a boolean to f32', () => { @@ -57,14 +53,7 @@ describe('f16', () => { expect(f16(10.5)).toBe(10.5); expect(f16(-10)).toBe(-10); expect(f16(-10.5)).toBe(-10.5); - expect(f16(65536)).toBe(Number.POSITIVE_INFINITY); // 65536 is too large to represent as an f16 - expect(f16(65504)).toBe(65504); // 65504 is the largest representable number - expect(f16(65535)).toBe(65504); // the largest number that is rounded down instead of being infinity - expect(f16(-65536)).toBe(Number.NEGATIVE_INFINITY); // -65536 is too small to represent as an f16 - expect(f16(-65505)).toBe(-65504); // -65504 is the smallest representable number - - expect(f16(5475)).closeTo(5475, 4); // at this range, the precision is 4 - expect(f16(5475)).not.toBe(5475); // the number is not exactly representable + expect(f16(65504)).toBe(65504); }); it('casts a boolean to f16', () => { @@ -72,3 +61,11 @@ describe('f16', () => { expect(f16(false)).toBe(0); }); }); + +describe('bool', () => { + it('correctly coerces to booleans', () => { + expect(bool(1)).toBe(true); + expect(bool(0)).toBe(false); + expect(bool(-4.1)).toBe(true); + }); +}); diff --git a/packages/typegpu/tests/std/numeric/mul.test.ts b/packages/typegpu/tests/std/numeric/mul.test.ts index 2bc291e24..d345fc7e8 100644 --- a/packages/typegpu/tests/std/numeric/mul.test.ts +++ b/packages/typegpu/tests/std/numeric/mul.test.ts @@ -97,8 +97,6 @@ describe('mul', () => { }); it('computes product of a vec4 and vec4', () => { - expect(vec2f(1, 2)).toEqual(vec2u(1, 2)); - expect(mul(vec4f(1, 2, 3, 4), vec4f(3, 4, 5, 6))).toEqual( vec4f(3, 8, 15, 24), ); diff --git a/packages/typegpu/tests/vector.test.ts b/packages/typegpu/tests/vector.test.ts index e44a3f7fa..5d657e3c0 100644 --- a/packages/typegpu/tests/vector.test.ts +++ b/packages/typegpu/tests/vector.test.ts @@ -6,6 +6,41 @@ import { sizeOf } from '../src/data/sizeOf.ts'; import tgpu from '../src/index.ts'; import { parse, parseResolved } from './utils/parseResolved.ts'; +describe('constructors', () => { + it('casts floats to signed integers', () => { + expect(d.vec2i(1.1, -1.1)).toStrictEqual(d.vec2i(1, -1)); + expect(d.vec3i(1.7, 2.6, 0.0)).toStrictEqual(d.vec3i(1, 2, 0)); + expect(d.vec4i(1.1, -1.1, -10.2, -1.0)).toStrictEqual( + d.vec4i(1, -1, -10, -1), + ); + }); + + it('casts floats to unsigned integers', () => { + expect(d.vec2u(1.1, -1)).toStrictEqual(d.vec2u(1, 4294967295)); + expect(d.vec3u(1.7, 2.6, 0.0)).toStrictEqual(d.vec3u(1, 2, 0)); + expect(d.vec4u(1.1, 1.1, 10.2, 1.0)).toStrictEqual(d.vec4u(1, 1, 10, 1)); + }); +}); + +describe('setters', () => { + it('coerces to singed integer values', () => { + const vec = d.vec4i(); + vec[0] = 1.1; + vec[1] = -1.1; + vec.z = 2.2; + vec.w = -2.2; + expect(vec).toStrictEqual(d.vec4i(1, -1, 2, -2)); + }); + + it('coerces to unsigned integer values', () => { + const vec = d.vec3u(); + vec[0] = 1.1; + vec[1] = -1.1; + vec.z = 2.2; + expect(vec).toStrictEqual(d.vec3u(1, 4294967295, 2)); + }); +}); + describe('vec2f', () => { it('should span 8 bytes', () => { expect(sizeOf(d.vec2f)).toBe(8); @@ -834,13 +869,13 @@ describe('v3i', () => { describe('v4f', () => { describe('(v3f, number) constructor', () => { it('works in JS', () => { - const red = d.vec3f(0.9, 0.2, 0.1); + const red = d.vec3f(0.125, 0.25, 0.375); const redWithAlpha = d.vec4f(red, 1); - expect(redWithAlpha).toStrictEqual(d.vec4f(0.9, 0.2, 0.1, 1)); + expect(redWithAlpha).toStrictEqual(d.vec4f(0.125, 0.25, 0.375, 1)); }); it('works in TGSL', () => { - const red = d.vec3f(0.9, 0.2, 0.1); + const red = d.vec3f(0.125, 0.25, 0.375); const main = tgpu['~unstable'] .fn([])(() => { @@ -857,7 +892,7 @@ describe('v4f', () => { fn main() { var green = vec3f(0, 1, 0); - var one = vec4f(vec3f(0.9, 0.2, 0.1), 1); + var one = vec4f(vec3f(0.125, 0.25, 0.375), 1); var two = vec4f(green, 1); var three = vec4f(vec3f(0, 0, 1), 1); } @@ -868,32 +903,32 @@ describe('v4f', () => { describe('(number, v3f) constructor', () => { it('works in JS', () => { - const foo = d.vec3f(0.2, 0.3, 0.4); + const foo = d.vec3f(0.25, 0.5, 0.75); const bar = d.vec4f(0.1, foo); - expect(bar).toStrictEqual(d.vec4f(0.1, 0.2, 0.3, 0.4)); + expect(bar).toStrictEqual(d.vec4f(0.1, 0.25, 0.5, 0.75)); }); it('works in TGSL', () => { - const foo = d.vec3f(0.2, 0.3, 0.4); + const foo = d.vec3f(0.25, 0.5, 0.75); const main = tgpu['~unstable'] .fn([])(() => { - const fooLocal = d.vec3f(0.2, 0.3, 0.4); + const fooLocal = d.vec3f(0.25, 0.5, 0.75); const one = d.vec4f(0.1, foo); // external const two = d.vec4f(0.1, fooLocal); // local variable - const three = d.vec4f(0.1, d.vec3f(0.2, 0.3, 0.4)); // literal + const three = d.vec4f(0.1, d.vec3f(0.25, 0.5, 0.75)); // literal }) .$name('main'); expect(parseResolved({ main })).toBe( parse(` fn main() { - var fooLocal = vec3f(0.2, 0.3, 0.4); + var fooLocal = vec3f(0.25, 0.5, 0.75); - var one = vec4f(0.1, vec3f(0.2, 0.3, 0.4)); + var one = vec4f(0.1, vec3f(0.25, 0.5, 0.75)); var two = vec4f(0.1, fooLocal); - var three = vec4f(0.1, vec3f(0.2, 0.3, 0.4)); + var three = vec4f(0.1, vec3f(0.25, 0.5, 0.75)); } `), );