diff --git a/packages/vectors3/src/gvec.ts b/packages/vectors3/src/gvec.ts new file mode 100644 index 0000000000..9a0ec00ff6 --- /dev/null +++ b/packages/vectors3/src/gvec.ts @@ -0,0 +1,99 @@ +import { range } from "@thi.ng/transducers/iter/range"; +import { map } from "@thi.ng/transducers/xform/map"; +import { Vec } from "./api"; + +/** + * Wrapper for strided, arbitrary length vectors. Wraps given buffer in + * ES6 `Proxy` with custom property getters/setters and implements ES6 + * `Iterable` interface. Furthermore, exposes read/write access to + * `IVector` properties: + * + * - `i` - start index + * - `s` - component stride + * - `buf` - backing buffer (readonly) + * - `length` - vector size + * + * Array index access uses bounds checking against [0..`size`) interval, + * but, for performance reasons, **not** against the actual wrapped + * buffer. + * + * Note: ES6 Proxy use is approx. 10x slower than standard array + * accesses. If several computations are to be performed on such vectors + * it will be much more efficient to first copy them to compact arrays + * and then copy result back if needed. + * + * ``` + * // 3D vector w/ stride length of 4 + * a = gvec([1,0,0,0,2,0,0,0,3,0,0,0], 3, 0, 4); + * a[0] // 1 + * a[1] // 2 + * a[2] // 3 + * + * [...a] + * // [1, 2, 3] + * + * add([], a, a) + * // [2, 4, 6] + * + * add() + * ``` + * + * @param buf + * @param size + * @param i + * @param s + */ +export const gvec = (buf: Vec, size: number, i = 0, s = 1) => + new Proxy(buf, { + get: (obj, id) => { + switch (id) { + case Symbol.iterator: + return function* () { + for (let j = 0, ii = i, ss = s; j < size; j++) { + yield obj[ii + j * ss]; + } + }; + case "length": + return size; + case "buf": + return buf; + case "i": + return i; + case "s": + return s; + default: + let j = parseInt(id); + return !isNaN(j) && j >= 0 && j < size ? + obj[i + j * s] : + undefined; + } + }, + set: (obj, id, value) => { + const j = parseInt(id); + if (!isNaN(j) && j >= 0 && j < size) { + obj[i + (id | 0) * s] = value; + } else { + switch (id) { + case "i": + i = value; + break; + case "s": + s = value; + break; + case "length": + size = value; + break; + default: + return false; + } + } + return true + }, + has: (_, id) => + (id >= 0 && id < size) || + id === "i" || + id == "s" || + id == "length", + ownKeys: () => + [...map(String, range(size)), "i", "s", "length"], + }); diff --git a/packages/vectors3/src/index.ts b/packages/vectors3/src/index.ts index 3d5d88a0ba..39e8e5372c 100644 --- a/packages/vectors3/src/index.ts +++ b/packages/vectors3/src/index.ts @@ -39,6 +39,7 @@ export * from "./exp"; export * from "./face-forward"; export * from "./floor"; export * from "./fract"; +export * from "./gvec"; export * from "./heading"; export * from "./homogeneous"; export * from "./invert"; diff --git a/packages/vectors3/src/map.ts b/packages/vectors3/src/map.ts index 0d871eeffb..dee9c78814 100644 --- a/packages/vectors3/src/map.ts +++ b/packages/vectors3/src/map.ts @@ -1,68 +1,127 @@ import { IVector, VecOpV, VecOpVV, VecOpVN, VecOpVVV, VecOpVVN } from "./api"; /** - * Like `mapBufferVV`, but for `VecOpV` type ops and hence only using - * single input. + * Vec2/3/4 view based buffer transformation for `VecOpVV` type ops and + * supporting arbitrary component and element layouts of all input and + * output buffers. The given pre-initialized vectors MUST be separate + * instances, are used as sliding cursors / views of their respective + * backing buffers and will be modified as part of the transformation + * process (though the input buffers themselves are treated as + * immutable, unless `out` is configured to use one of the input + * buffers). + * + * In each iteration `op` is called via `op(out, a, b)`, followed by + * cursor updates to process the next vector. No bounds checking is + * performed. + * + * This function returns `out`'s backing buffer. * * ``` - * // 4x 2D vectors in SOA layout, i.e. [x1,x2,x3,x4,y1,y2,y3,y4] - * buf = [1,3,5,7,2,4,6,8]; + * // each input buffer contains 2 2D vectors, but using + * // different strided data layouts + * mapBufferVV( + * // transformation function + * add, + * // init output buffer view + * new Vec2(), + * // wrap 1st input buffer & configure offset & component stride + * new Vec2([1,0,2,0,0,0,0,0,3,0,4,0,0,0,0,0], 0, 2), + * // wrap 2nd input buffer + * new Vec2([0,10,0,0,20,0,0,30,0,0,40], 1, 3), + * 2, // num vectors + * 2, // output element stride + * 8, // input #1 element stride + * 6 // input #2 element stride + * ); + * // [ 11, 22, 33, 44 ] + * ``` * - * // use `swapXY` to swizzle each vector and use AOS for output - * res = mapBufferV(swapXY, new Vec2(), new Vec2(buf,0,4), 4, 2, 1); - * // [ 2, 1, 4, 3, 6, 5, 8, 7 ] + * `Vec2/3/4.iterator()` combined with transducers can be used to + * achieve the same (and more flexible) transformations, but will incur + * more intermediate object allocations. `mapBuffer*()` functions only + * use (and mutate) the provided vector instances and do not allocate + * any further objects. * - * // unpack result for demonstration purposes - * [...Vec2.iterator(res, 4)].map(v => [...v]); - * // [ [ 2, 1 ], [ 4, 3 ], [ 6, 5 ], [ 8, 7 ] ] + * ``` + * // output buffer + * const out = new Array(4); + * + * tx.run( + * tx.map(([o, a, b]) => add(o, a, b)), + * tx.tuples( + * Vec2.iterator(out, 2), + * Vec2.iterator([1,0,2,0,0,0,0,0,3,0,4,0,0,0,0,0], 2, 0, 2, 8), + * Vec2.iterator([0,10,0,0,20,0,0,30,0,0,40], 2, 1, 3, 6), + * ) + * ); + * + * out + * // [ 11, 22, 33, 44 ] * ``` * * @param op * @param out * @param a + * @param b * @param num * @param so * @param sa + * @param sb */ -export const mapBufferV = ( - op: VecOpV, +export const mapBufferVV = ( + op: VecOpVV, out: IVector, a: IVector, + b: IVector, num: number, so = out.length * out.s, - sa = a.length * a.s) => { + sa = a.length * a.s, + sb = b.length * b.s) => { while (num-- > 0) { - op(out, a); + op(out, a, b); out.i += so; a.i += sa; + b.i += sb; } return out.buf; }; /** - * Like `mapBufferVV`, but for `VecOpVN` type ops and hence using - * a single vector input buffer `a` and a scalar `n`. + * Like `mapBufferVV`, but for `VecOpV` type ops and hence only using + * single input. + * + * ``` + * // 4x 2D vectors in SOA layout + * // i.e. [x1, x2, x3, x4, y1, y2, y3, y4] + * buf = [1, 3, 5, 7, 2, 4, 6, 8]; + * + * // use `swapXY` to swizzle each vector and use AOS for output + * res = mapBufferV(swapXY, new Vec2(), new Vec2(buf, 0, 4), 4, 2, 1); + * // [ 2, 1, 4, 3, 6, 5, 8, 7 ] + * + * // unpack result for demonstration purposes + * [...Vec2.iterator(res, 4)].map(v => [...v]); + * // [ [ 2, 1 ], [ 4, 3 ], [ 6, 5 ], [ 8, 7 ] ] + * ``` * * @param op * @param out * @param a - * @param n * @param num * @param so * @param sa */ -export const mapBufferVN = ( - op: VecOpVN, +export const mapBufferV = ( + op: VecOpV, out: IVector, a: IVector, - n: number, num: number, so = out.length * out.s, sa = a.length * a.s) => { while (num-- > 0) { - op(out, a, n); + op(out, a); out.i += so; a.i += sa; } @@ -70,65 +129,30 @@ export const mapBufferVN = ( }; /** - * Vec2/3/4 view based buffer transformation for `VecOpVV` type ops and - * supporting arbitrary component and element layouts of all input and - * output buffers. The given pre-initialized vectors MUST be separate - * instances, are used as sliding cursors / views of their respective - * backing buffers and will be modified as part of the transformation - * process (though the input buffers themselves are treated as - * immutable, unless `out` is configured to use one of the input - * buffers). - * - * In each iteration `op` is called via `op(out, a, b)`, followed by - * cursor updates to process the next vector. No bounds checking is - * performed. - * - * This function returns `out`'s backing buffer. - * - * ``` - * // each input buffer contains 2 2D vectors, but using - * // different strided data layouts - * mapBufferVV( - * // transformation function - * add, - * // init output buffer view - * new Vec2(), - * // wrap 1st input buffer & configure offset & component stride - * new Vec2([1,0,2,0,0,0,0,0,3,0,4,0,0,0,0,0], 0, 2), - * // wrap 2nd input buffer - * new Vec2([0,10,0,0,20,0,0,30,0,0,40], 1, 3), - * 2, // num vectors - * 2, // output element stride - * 8, // input #1 element stride - * 6 // input #2 element stride - * ); - * // [ 11, 22, 33, 44 ] - * ``` + * Like `mapBufferVV`, but for `VecOpVN` type ops and hence using + * a single vector input buffer `a` and a scalar `n`. * * @param op * @param out * @param a - * @param b + * @param n * @param num * @param so * @param sa - * @param sb */ -export const mapBufferVV = ( - op: VecOpVV, +export const mapBufferVN = ( + op: VecOpVN, out: IVector, a: IVector, - b: IVector, + n: number, num: number, so = out.length * out.s, - sa = a.length * a.s, - sb = b.length * b.s) => { + sa = a.length * a.s) => { while (num-- > 0) { - op(out, a, b); + op(out, a, n); out.i += so; a.i += sa; - b.i += sb; } return out.buf; };