export class Vector {
constructor(...components) {
this.components = [...components];
}
toString() {
return `[${this.components.join(', ')}]`;
}
get dimensions() {
return this.components.length;
}
get x() {
return this.components[0];
}
get y() {
return this.components[1];
}
get z() {
return this.components[2];
}
get t() {
return this.components[3];
}
get length() {
return Math.sqrt(
this.components.map((x) => x ** 2).reduce((x, y) => x + y, 0),
);
}
get angle() {
assertFixedComponentCount(this, 2);
let deltaRotation = 0;
if (this.x < 0) {
if (this.y <= 0) {
deltaRotation += Math.PI;
} else {
deltaRotation += Math.PI / 2;
}
} else if (this.y < 0) {
if (this.x === 0) {
deltaRotation += Math.PI;
} else {
deltaRotation += (3 * Math.PI) / 2;
}
}
return Math.atan(Math.abs(this.y) / Math.abs(this.x)) + deltaRotation;
}
add(v) {
assertSameComponentCount(this, v);
return new Vector(
...this.components.map((x, index) => x + v.components[index]),
);
}
}
function assertFixedComponentCount(v, componentCount) {
if (v.dimensions !== componentCount) {
throw new RangeError(
`Expected ${componentCount} components, but found ${v.dimensions}`,
);
}
}
function assertSameComponentCount(v1, v2) {
if (v1.dimensions !== v2.dimensions) {
throw new RangeError(
`Different number of components: ${v1.dimensions} vs. ${v2.dimensions}`,
);
}
}
<script type="module">
import '/util/test.js';
describe('Vector', () => {
describe('components', () => {
it('provides all given components', () => {
const v = new Vector(1, 2);
expect(v.components).to.deep.equal([1, 2]);
});
it('can be expressed as string', () => {
const v = new Vector(2, 1);
expect(v.toString()).to.equal('[2, 1]');
});
it('has easy access to first four dimensions', () => {
const v = new Vector(1, 2, 3, 4);
expect(v.components[0]).to.equal(v.x);
expect(v.components[1]).to.equal(v.y);
expect(v.components[2]).to.equal(v.z);
expect(v.components[3]).to.equal(v.t);
});
});
describe('#length', () => {
it('works for 2D vectors', () => {
const v = new Vector(0, 1);
expect(v.length).to.equal(1);
});
it('works for any number of dimensions', () => {
const v = new Vector(2, 2, 2, 2);
expect(v.length).to.equal(4);
});
});
describe('#angle', () => {
it('gives correct angle', () => {
const v = new Vector(1, 1);
expect(v.angle).to.equal(Math.PI / 4);
});
it('throws RangeError for wrong dimensions', () => {
const v = new Vector(1, 2, 3);
expect(() => v.angle).to.throw(RangeError);
});
});
describe('#add()', () => {
it('returns a new Vector with all added components', () => {
const v1 = new Vector(1, 0);
const v2 = new Vector(0, 1);
expect(v1.add(v2)).to.deep.equal(new Vector(1, 1));
});
it('throws RangeError for different dimensions', () => {
const v1 = new Vector(1, 0);
const v2 = new Vector(0, 1, 1);
expect(() => v1.add(v2)).to.throw(RangeError);
});
});
});
</script>