Skip to content

Instantly share code, notes, and snippets.

@mrjacobbloom
Last active March 14, 2019 23:47
Show Gist options
  • Save mrjacobbloom/846e95a5324ef1ca94d5ab2afd8df80f to your computer and use it in GitHub Desktop.
Save mrjacobbloom/846e95a5324ef1ca94d5ab2afd8df80f to your computer and use it in GitHub Desktop.
Use JS proxies to make a `vec(1,2,3,4)` class that allows for swizzling like in GLSL -- https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Swizzling
export class Vec {
constructor(...values) {
this.values = this.constructor.flatten(values);
this.self = this;
this.proxy = new Proxy(this, this.constructor.handler);
return this.proxy;
}
static flatten(values) {
const flatValues = [];
for(const value of values) {
if(this.isVec(value)) {
flatValues.push(...value);
} else {
flatValues.push(value);
}
}
return flatValues;
}
get length() {
return this.self.values.length;
}
get [Symbol.iterator]() {
return this.self.values[Symbol.iterator];
}
toString() {
return `vec(${this.self.values.join(', ')})`;
}
static isVec(value) {
return value && value.self && value.self instanceof Vec;
}
static swizzleRegexp = /^(?:[xyzw]+|[rgba]+|[stpq]+)$/;
static swizzleMap = {
x: 0, r: 0, s: 0,
y: 1, g: 1, t: 1,
z: 2, b: 2, p: 2,
w: 3, a: 3, q: 3,
};
static swizzleToIndexes(swizzle) {
return [...swizzle].map(letter => this.swizzleMap[letter]);
}
static publicProps = ['values', 'length', 'self', Symbol.iterator, 'toString'];
static handler = {
get(target, prop) {
if(typeof prop == 'string' && target.constructor.swizzleRegexp.test(prop)) {
const idxs = target.constructor.swizzleToIndexes(prop);
if(idxs.length == 1) {
return target.values[idxs[0]];
} else {
return vec(...idxs.map(idx => target.values[idx]));
}
} else if(target.constructor.publicProps.includes(prop)) {
return Reflect.get(...arguments);
} else if(typeof prop != 'symbol' && !Number.isNaN(parseInt(prop))) {
return target.values[parseInt(prop)];
} else {
return undefined;
}
},
set(target, prop, value) {
if(typeof prop != 'symbol' && target.constructor.swizzleRegexp.test(prop)) {
const idxs = target.constructor.swizzleToIndexes(prop);
if(idxs.length == 1) {
if(target.constructor.isVec(value)) {
if(value.length == 1) {
target.values[idxs[0]] = value[0];
} else {
throw new RangeError(`Cannot set 1 component to a vec of size ${value.length}`);
}
} else {
target.values[idxs[0]] = value;
}
return true;
} else {
if(!target.constructor.isVec(value)) {
throw new TypeError('A multi-component swizzle can only be set to another vec()');
}
if(value.length !== idxs.length) {
throw new RangeError(`Cannot set ${idxs.length} components to a vec of size ${value.length}`);
}
// todo flatten here?
for(let i = 0; i < idxs.length; i++) {
target.values[idxs[i]] = value[i];
}
return true;
}
} else if(typeof prop != 'symbol' && !Number.isNaN(parseInt(prop))) {
target.values[prop] = value;
return true;
} else {
return Reflect.set(...arguments);
}
}
};
}
export function vec(...values) {
return new Vec(...values);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment