Last active
March 14, 2019 23:47
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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