diff --git a/app/lib/world/vector.coffee b/app/lib/world/vector.coffee index 0b826b6c0..913d2b66e 100644 --- a/app/lib/world/vector.coffee +++ b/app/lib/world/vector.coffee @@ -5,10 +5,14 @@ class Vector for name in ['add', 'subtract', 'multiply', 'divide', 'limit', 'normalize', 'rotate'] do (name) -> Vector[name] = (a, b, useZ) -> - a.copy()[name](b, useZ) + a.copy()["#{name}Self"](b, useZ) + for name in ['magnitude', 'heading', 'distance', 'dot', 'equals', 'copy', 'distanceSquared'] + do (name) -> + Vector[name] = (a, b, useZ) -> + a[name](b, useZ) isVector: true - apiProperties: ['x', 'y', 'z', 'magnitude', 'heading', 'distance', 'dot', 'equals', 'copy', 'distanceSquared', 'rotate'] + apiProperties: ['x', 'y', 'z', 'magnitude', 'heading', 'distance', 'dot', 'equals', 'copy', 'distanceSquared', 'rotate', 'add', 'subtract', 'multiply', 'divide', 'limit', 'normalize', 'rotate'] constructor: (x=0, y=0, z=0) -> return new Vector x, y, z unless @ instanceof Vector @@ -17,6 +21,74 @@ class Vector copy: -> new Vector(@x, @y, @z) + + # Mutating methods: + + normalizeSelf: (useZ) -> + m = @magnitude useZ + @divideSelf m, useZ if m > 0 + @ + + normalize: (useZ) -> + # Hack to detect when we are in player code so we can avoid mutation + (if @__aetherAPIValue? then @copy() else @).normalizeSelf(useZ) + + limitSelf: (max) -> + if @magnitude() > max + @normalizeSelf() + @multiplySelf(max) + else + @ + + limit: (useZ) -> + (if @__aetherAPIValue? then @copy() else @).limitSelf(useZ) + + subtractSelf: (other, useZ) -> + @x -= other.x + @y -= other.y + @z -= other.z if useZ + @ + + subtract: (other, useZ) -> + (if @__aetherAPIValue? then @copy() else @).subtractSelf(other, useZ) + + addSelf: (other, useZ) -> + @x += other.x + @y += other.y + @z += other.z if useZ + @ + + add: (other, useZ) -> + (if @__aetherAPIValue? then @copy() else @).addSelf(other, useZ) + + divideSelf: (n, useZ) -> + [@x, @y] = [@x / n, @y / n] + @z = @z / n if useZ + @ + + divide: (n, useZ) -> + (if @__aetherAPIValue? then @copy() else @).divideSelf(n, useZ) + + multiplySelf: (n, useZ) -> + [@x, @y] = [@x * n, @y * n] + @z = @z * n if useZ + @ + + multiply: (n, useZ) -> + (if @__aetherAPIValue? then @copy() else @).multiplySelf(n, useZ) + + # Rotate it around the origin + # If we ever want to make this also use z: https://en.wikipedia.org/wiki/Axes_conventions + rotateSelf: (theta) -> + return @ unless theta + [@x, @y] = [Math.cos(theta) * @x - Math.sin(theta) * @y, Math.sin(theta) * @x + Math.cos(theta) * @y] + @ + + rotate: (theta) -> + (if @__aetherAPIValue? then @copy() else @).rotateSelf(theta) + + # Non-mutating methods: + magnitude: (useZ) -> sum = @x * @x + @y * @y sum += @z * @z if useZ @@ -27,18 +99,6 @@ class Vector sum += @z * @z if useZ sum - normalize: (useZ) -> - m = @magnitude useZ - @divide m, useZ if m > 0 - @ - - limit: (max) -> - if @magnitude() > max - @normalize() - return @multiply(max) - else - @ - heading: -> -1 * Math.atan2(-1 * @y, @x) @@ -60,28 +120,6 @@ class Vector sum += dz * dz sum - subtract: (other, useZ) -> - @x -= other.x - @y -= other.y - @z -= other.z if useZ - @ - - add: (other, useZ) -> - @x += other.x - @y += other.y - @z += other.z if useZ - @ - - divide: (n, useZ) -> - [@x, @y] = [@x / n, @y / n] - @z = @z / n if useZ - @ - - multiply: (n, useZ) -> - [@x, @y] = [@x * n, @y * n] - @z = @z * n if useZ - @ - dot: (other, useZ) -> sum = @x * other.x + @y * other.y sum += @z + other.z if useZ @@ -89,7 +127,7 @@ class Vector # Not the strict projection, the other isn't converted to a unit vector first. projectOnto: (other, useZ) -> - other.copy().multiply(@dot(other, useZ), useZ) + other.copy().multiplySelf(@dot(other, useZ), useZ) isZero: (useZ) -> result = @x is 0 and @y is 0 @@ -101,13 +139,6 @@ class Vector result = result and @z is other.z if useZ result - # Rotate it around the origin - # If we ever want to make this also use z: https://en.wikipedia.org/wiki/Axes_conventions - rotate: (theta) -> - return @ unless theta - [@x, @y] = [Math.cos(theta) * @x - Math.sin(theta) * @y, Math.sin(theta) * @x + Math.cos(theta) * @y] - @ - invalid: () -> return (@x is Infinity) || isNaN(@x) || @y is Infinity || isNaN(@y) || @z is Infinity || isNaN(@z) diff --git a/test/app/lib/world/vector.spec.coffee b/test/app/lib/world/vector.spec.coffee index ac5d017d1..b12c3f0e9 100644 --- a/test/app/lib/world/vector.spec.coffee +++ b/test/app/lib/world/vector.spec.coffee @@ -27,3 +27,57 @@ describe 'Vector', -> v2 = v.copy() v2.rotate -0.0000001 * Math.PI expect(v.distance v2).toBeCloseTo 0 + + it 'has class methods equivalent to the instance methods', -> + expectEquivalentMethods = (method, arg) -> + v = new Vector 7, 7 + classResult = Vector[method](v, arg) + instanceResult = v[method](arg) + expect(classResult).toEqual instanceResult + + expectEquivalentMethods 'add', new Vector 1, 1 + expectEquivalentMethods 'subtract', new Vector 3, 3 + expectEquivalentMethods 'multiply', 4 + expectEquivalentMethods 'divide', 2 + expectEquivalentMethods 'limit', 3 + expectEquivalentMethods 'normalize' + expectEquivalentMethods 'rotate', 0.3 + expectEquivalentMethods 'magnitude' + expectEquivalentMethods 'heading' + expectEquivalentMethods 'distance', new Vector 2, 2 + expectEquivalentMethods 'distanceSquared', new Vector 4, 4 + expectEquivalentMethods 'dot', new Vector 3, 3 + expectEquivalentMethods 'equals', new Vector 7, 7 + expectEquivalentMethods 'copy' + + it "doesn't mutate when in player code", -> + expectNoMutation = (fn) -> + v = new Vector 5, 5 + # player code detection hack depends on this property being != null + v.__aetherAPIValue = {} + v2 = fn v + expect(v.x).toEqual 5 + expect(v).not.toBe v2 + + expectNoMutation (v) -> v.normalize() + expectNoMutation (v) -> v.limit 2 + expectNoMutation (v) -> v.subtract new Vector 2, 2 + expectNoMutation (v) -> v.add new Vector 2, 2 + expectNoMutation (v) -> v.divide 2 + expectNoMutation (v) -> v.multiply 2 + expectNoMutation (v) -> v.rotate 0.5 + + it 'mutates when not in player code', -> + expectMutation = (fn) -> + v = new Vector 5, 5 + v2 = fn v + expect(v.x).not.toEqual 5 + expect(v).toBe v2 + + expectMutation (v) -> v.normalize() + expectMutation (v) -> v.limit 2 + expectMutation (v) -> v.subtract new Vector 2, 2 + expectMutation (v) -> v.add new Vector 2, 2 + expectMutation (v) -> v.divide 2 + expectMutation (v) -> v.multiply 2 + expectMutation (v) -> v.rotate 0.5