mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Merged in geometry work from #51.
This commit is contained in:
parent
855461a67e
commit
42af807e5c
12 changed files with 556 additions and 38 deletions
|
@ -96,7 +96,9 @@ Aether.addGlobal('_', _);
|
||||||
var serializedClasses = {
|
var serializedClasses = {
|
||||||
"Thang": self.require('lib/world/thang'),
|
"Thang": self.require('lib/world/thang'),
|
||||||
"Vector": self.require('lib/world/vector'),
|
"Vector": self.require('lib/world/vector'),
|
||||||
"Rectangle": self.require('lib/world/rectangle')
|
"Rectangle": self.require('lib/world/rectangle'),
|
||||||
|
"Ellipse": self.require('lib/world/ellipse'),
|
||||||
|
"LineSegment": self.require('lib/world/line_segment')
|
||||||
};
|
};
|
||||||
self.currentUserCodeMapCopy = "";
|
self.currentUserCodeMapCopy = "";
|
||||||
self.currentDebugWorldFrame = 0;
|
self.currentDebugWorldFrame = 0;
|
||||||
|
|
|
@ -181,12 +181,11 @@ module.exports = class Mark extends CocoClass
|
||||||
buildDebug: ->
|
buildDebug: ->
|
||||||
@mark = new createjs.Shape()
|
@mark = new createjs.Shape()
|
||||||
PX = 3
|
PX = 3
|
||||||
[w, h] = [Math.max(PX, @sprite.thang.width * Camera.PPM), Math.max(PX, @sprite.thang.height * Camera.PPM) * @camera.y2x]
|
[w, h] = [Math.max(PX, @sprite.thang.width * Camera.PPM), Math.max(PX, @sprite.thang.height * Camera.PPM) * @camera.y2x] # TODO: doesn't work with rotation
|
||||||
@mark.alpha = 0.5
|
@mark.alpha = 0.5
|
||||||
@mark.graphics.beginFill '#abcdef'
|
@mark.graphics.beginFill '#abcdef'
|
||||||
if @sprite.thang.shape in ['ellipsoid', 'disc']
|
if @sprite.thang.shape in ['ellipsoid', 'disc']
|
||||||
[w, h] = [Math.max(PX, w, h), Math.max(PX, w, h)]
|
@mark.graphics.drawEllipse -w / 2, -h / 2, w, h
|
||||||
@mark.graphics.drawCircle 0, 0, w / 2
|
|
||||||
else
|
else
|
||||||
@mark.graphics.drawRect -w / 2, -h / 2, w, h
|
@mark.graphics.drawRect -w / 2, -h / 2, w, h
|
||||||
@mark.graphics.endFill()
|
@mark.graphics.endFill()
|
||||||
|
@ -259,7 +258,7 @@ module.exports = class Mark extends CocoClass
|
||||||
|
|
||||||
updateRotation: ->
|
updateRotation: ->
|
||||||
if @name is 'debug' or (@name is 'shadow' and @sprite.thang?.shape in ['rectangle', 'box'])
|
if @name is 'debug' or (@name is 'shadow' and @sprite.thang?.shape in ['rectangle', 'box'])
|
||||||
@mark.rotation = @sprite.thang.rotation * 180 / Math.PI
|
@mark.rotation = -@sprite.thang.rotation * 180 / Math.PI
|
||||||
|
|
||||||
updateScale: ->
|
updateScale: ->
|
||||||
if @name is 'bounds' and ((@sprite.thang.width isnt @lastWidth or @sprite.thang.height isnt @lastHeight) or (@sprite.thang.drawsBoundsIndex isnt @drawsBoundsIndex))
|
if @name is 'bounds' and ((@sprite.thang.width isnt @lastWidth or @sprite.thang.height isnt @lastHeight) or (@sprite.thang.drawsBoundsIndex isnt @drawsBoundsIndex))
|
||||||
|
|
174
app/lib/world/ellipse.coffee
Normal file
174
app/lib/world/ellipse.coffee
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
Vector = require './vector'
|
||||||
|
LineSegment = require './line_segment'
|
||||||
|
Rectangle = require './rectangle'
|
||||||
|
|
||||||
|
class Ellipse
|
||||||
|
@className: "Ellipse"
|
||||||
|
|
||||||
|
# TODO: add class methods for add, multiply, subtract, divide, rotate
|
||||||
|
|
||||||
|
isEllipse: true
|
||||||
|
apiProperties: ['x', 'y', 'width', 'height', 'rotation', 'distanceToPoint', 'distanceSquaredToPoint', 'distanceToRectangle', 'distanceSquaredToRectangle', 'distanceToEllipse', 'distanceSquaredToEllipse', 'distanceToShape', 'distanceSquaredToShape', 'containsPoint', 'intersectsLineSegment', 'intersectsRectangle', 'intersectsEllipse', 'getPos', 'containsPoint', 'copy']
|
||||||
|
|
||||||
|
constructor: (@x=0, @y=0, @width=0, @height=0, @rotation=0) ->
|
||||||
|
|
||||||
|
copy: ->
|
||||||
|
new Ellipse(@x, @y, @width, @height, @rotation)
|
||||||
|
|
||||||
|
getPos: ->
|
||||||
|
new Vector(@x, @y)
|
||||||
|
|
||||||
|
rectangle: ->
|
||||||
|
new Rectangle(@x, @y, @width, @height, @rotation)
|
||||||
|
|
||||||
|
axisAlignedBoundingBox: (rounded=true) ->
|
||||||
|
@rectangle().axisAlignedBoundingBox()
|
||||||
|
|
||||||
|
distanceToPoint: (p) ->
|
||||||
|
@rectangle().distanceToPoint p # TODO: actually implement ellipse ellipse-point distance
|
||||||
|
|
||||||
|
distanceSquaredToPoint: (p) ->
|
||||||
|
# Doesn't handle rotation; just supposed to be faster than distanceToPoint.
|
||||||
|
@rectangle().distanceSquaredToPoint p # TODO: actually implement ellipse-point distance
|
||||||
|
|
||||||
|
distanceToRectangle: (other) ->
|
||||||
|
Math.sqrt @distanceSquaredToRectangle other
|
||||||
|
|
||||||
|
distanceSquaredToRectangle: (other) ->
|
||||||
|
@rectangle().distanceSquaredToRectangle other # TODO: actually implement ellipse-rectangle distance
|
||||||
|
|
||||||
|
distanceToEllipse: (ellipse) ->
|
||||||
|
Math.sqrt @distanceSquaredToEllipse ellipse
|
||||||
|
|
||||||
|
distanceSquaredToEllipse: (ellipse) ->
|
||||||
|
@rectangle().distanceSquaredToEllipse ellipse # TODO: actually implement ellipse-ellipse distance
|
||||||
|
|
||||||
|
distanceToShape: (shape) ->
|
||||||
|
Math.sqrt @distanceSquaredToShape shape
|
||||||
|
|
||||||
|
distanceSquaredToShape: (shape) ->
|
||||||
|
if shape.isEllipse then @distanceSquaredToEllipse shape else @distanceSquaredToRectangle shape
|
||||||
|
|
||||||
|
containsPoint: (p, withRotation=true) ->
|
||||||
|
[a, b] = [@width / 2, @height / 2]
|
||||||
|
[h, k] = [@x, @y]
|
||||||
|
[x, y] = [p.x, p.y]
|
||||||
|
x2 = Math.pow(x, 2)
|
||||||
|
a2 = Math.pow(a, 2)
|
||||||
|
a4 = Math.pow(a, 4)
|
||||||
|
b2 = Math.pow(b, 2)
|
||||||
|
b4 = Math.pow(b, 4)
|
||||||
|
h2 = Math.pow(h, 2)
|
||||||
|
k2 = Math.pow(k, 2)
|
||||||
|
if withRotation and @rotation
|
||||||
|
sint = Math.sin(@rotation)
|
||||||
|
sin2t = Math.sin(2 * @rotation)
|
||||||
|
cost = Math.cos(@rotation)
|
||||||
|
cos2t = Math.cos(2 * @rotation)
|
||||||
|
numeratorLeft = (-a2 * h * sin2t) + (a2 * k * cos2t) + (a2 * k) + (a2 * x * sin2t)
|
||||||
|
numeratorMiddle = Math.SQRT2 * Math.sqrt((a4 * b2 * cos2t) + (a4 * b2) - (a2 * b4 * cos2t) + (a2 * b4) - (2 * a2 * b2 * h2) + (4 * a2 * b2 * h * x) - (2 * a2 * b2 * x2))
|
||||||
|
numeratorRight = (b2 * h * sin2t) - (b2 * k * cos2t) + (b2 * k) - (b2 * x * sin2t)
|
||||||
|
denominator = (a2 * cos2t) + a2 - (b2 * cos2t) + b2
|
||||||
|
solution1 = (numeratorLeft - numeratorMiddle + numeratorRight) / denominator
|
||||||
|
solution2 = (numeratorLeft + numeratorMiddle + numeratorRight) / denominator
|
||||||
|
if (not isNaN solution1) and (not isNaN solution2)
|
||||||
|
[bigSolution, littleSolution] = if solution1 > solution2 then [solution1, solution2] else [solution2, solution1]
|
||||||
|
if y > littleSolution and y < bigSolution
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
numeratorLeft = a2 * k
|
||||||
|
numeratorRight = Math.sqrt((a4 * b2) - (a2 * b2 * h2) + (2 * a2 * b2 * h * x) - (a2 * b2 * x2))
|
||||||
|
denominator = a2
|
||||||
|
solution1 = (numeratorLeft + numeratorRight) / denominator
|
||||||
|
solution2 = (numeratorLeft - numeratorRight) / denominator
|
||||||
|
if (not isNaN solution1) and (not isNaN solution2)
|
||||||
|
[bigSolution, littleSolution] = if solution1 > solution2 then [solution1, solution2] else [solution2, solution1]
|
||||||
|
if y > littleSolution and y < bigSolution
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
false
|
||||||
|
|
||||||
|
intersectsLineSegment: (p1, p2) ->
|
||||||
|
[px1, py1, px2, py2] = [p1.x, p1.y, p2.x, p2.y]
|
||||||
|
m = (py1 - py2) / (px1 - px2)
|
||||||
|
m2 = Math.pow(m, 2)
|
||||||
|
c = py1 - (m * px1)
|
||||||
|
c2 = Math.pow(c, 2)
|
||||||
|
[a, b] = [@width / 2, @height / 2]
|
||||||
|
[h, k] = [@x, @y]
|
||||||
|
a2 = Math.pow(a, 2)
|
||||||
|
a4 = Math.pow(a, 2)
|
||||||
|
b2 = Math.pow(b, 2)
|
||||||
|
b4 = Math.pow(b, 4)
|
||||||
|
h2 = Math.pow(h, 2)
|
||||||
|
k2 = Math.pow(k, 2)
|
||||||
|
sint = Math.sin(@rotation)
|
||||||
|
sin2t = Math.sin(2 * @rotation)
|
||||||
|
cost = Math.cos(@rotation)
|
||||||
|
cos2t = Math.cos(2 * @rotation)
|
||||||
|
if (not isNaN m) and m != Infinity and m != -Infinity
|
||||||
|
numeratorLeft = (-a2 * c * m * cos2t) - (a2 * c * m) + (a2 * c * sin2t) - (a2 * h * m * sin2t) - (a2 * h * cos2t) + (a2 * h) + (a2 * k * m * cos2t) + (a2 * k * m) - (a2 * k * sin2t)
|
||||||
|
numeratorMiddle = Math.SQRT2 * Math.sqrt((a4 * b2 * m2 * cos2t) + (a4 * b2 * m2) - (2 * a4 * b2 * m * sin2t) - (a4 * b2 * cos2t) + (a4 * b2) - (a2 * b4 * m2 * cos2t) + (a2 * b4 * m2) + (2 * a2 * b4 * m * sin2t) + (a2 * b4 * cos2t) + (a2 * b4) - (2 * a2 * b2 * c2) - (4 * a2 * b2 * c * h * m) + (4 * a2 * b2 * c * k) - (2 * a2 * b2 * h2 * m2) + (4 * a2 * b2 * h * k * m) - (2 * a2 * b2 * k2))
|
||||||
|
numeratorRight = (b2 * c * m * cos2t) - (b2 * c * m) - (b2 * c * sin2t) + (b2 * h * m * sin2t) + (b2 * h * cos2t) + (b2 * h) - (b2 * k * m * cos2t) + (b2 * k * m) + (b2 * k * sin2t)
|
||||||
|
denominator = (a2 * m2 * cos2t) + (a2 * m2) - (2 * a2 * m * sin2t) - (a2 * cos2t) + a2 - (b2 * m2 * cos2t) + (b2 * m2) + (2 * b2 * m * sin2t) + (b2 * cos2t) + b2
|
||||||
|
solution1 = (-numeratorLeft - numeratorMiddle + numeratorRight) / denominator
|
||||||
|
solution2 = (-numeratorLeft + numeratorMiddle + numeratorRight) / denominator
|
||||||
|
if (not isNaN solution1) and (not isNaN solution2)
|
||||||
|
[littleX, bigX] = if px1 < px2 then [px1, px2] else [px2, px1]
|
||||||
|
if (littleX <= solution1 and bigX >= solution1) or (littleX <= solution2 and bigX >= solution2)
|
||||||
|
return true
|
||||||
|
if (not isNaN solution1) or (not isNaN solution2)
|
||||||
|
solution = if not isNaN solution1 then solution1 else solution2
|
||||||
|
[littleX, bigX] = if px1 < px2 then [px1, px2] else [px2, px1]
|
||||||
|
if littleX <= solution and bigX >= solution
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
x = px1
|
||||||
|
x2 = Math.pow(x, 2)
|
||||||
|
numeratorLeft = (-a2 * h * sin2t) + (a2 * k * cos2t) + (a2 * k) + (a2 * x * sin2t)
|
||||||
|
numeratorMiddle = Math.SQRT2 * Math.sqrt((a4 * b2 * cos2t) + (a4 * b2) - (a2 * b4 * cos2t) + (a2 * b4) - (2 * a2 * b2 * h2) + (4 * a2 * b2 * h * x) - (2 * a2 * b2 * x2))
|
||||||
|
numeratorRight = (b2 * h * sin2t) - (b2 * k * cos2t) + (b2 * k) - (b2 * x * sin2t)
|
||||||
|
denominator = (a2 * cos2t) + a2 - (b2 * cos2t) + b2
|
||||||
|
solution1 = (numeratorLeft - numeratorMiddle + numeratorRight) / denominator
|
||||||
|
solution2 = (numeratorLeft + numeratorMiddle + numeratorRight) / denominator
|
||||||
|
if (not isNaN solution1) or (not isNaN solution2)
|
||||||
|
solution = if not isNaN solution1 then solution1 else solution2
|
||||||
|
[littleY, bigY] = if py1 < py2 then [py1, py2] else [py2, py1]
|
||||||
|
if littleY <= solution and bigY >= solution
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
false
|
||||||
|
|
||||||
|
intersectsRectangle: (rectangle) ->
|
||||||
|
rectangle.intersectsEllipse @
|
||||||
|
|
||||||
|
intersectsEllipse: (ellipse) ->
|
||||||
|
@rectangle().intersectsEllipse @ # TODO: actually implement ellipse-ellipse intersection
|
||||||
|
#return true if @containsPoint ellipse.getPos()
|
||||||
|
|
||||||
|
intersectsShape: (shape) ->
|
||||||
|
if shape.isEllipse then @intersectsEllipse shape else @intersectsRectangle shape
|
||||||
|
|
||||||
|
toString: ->
|
||||||
|
return "{x: #{@x.toFixed(0)}, y: #{@y.toFixed(0)}, w: #{@width.toFixed(0)}, h: #{@height.toFixed(0)}, rot: #{@rotation.toFixed(3)}}"
|
||||||
|
|
||||||
|
serialize: ->
|
||||||
|
{CN: @constructor.className, x: @x, y: @y, w: @width, h: @height, r: @rotation}
|
||||||
|
|
||||||
|
@deserialize: (o, world, classMap) ->
|
||||||
|
new Ellipse o.x, o.y, o.w, o.h, o.r
|
||||||
|
|
||||||
|
serializeForAether: -> @serialize()
|
||||||
|
@deserializeFromAether: (o) -> @deserialize o
|
||||||
|
|
||||||
|
module.exports = Ellipse
|
80
app/lib/world/line_segment.coffee
Normal file
80
app/lib/world/line_segment.coffee
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
class LineSegment
|
||||||
|
@className: "LineSegment"
|
||||||
|
|
||||||
|
constructor: (@a, @b) ->
|
||||||
|
@slope = (@a.y - @b.y) / (@a.x - @b.x)
|
||||||
|
@y0 = @a.y - (@slope * @a.x)
|
||||||
|
@left = if @a.x < @b.x then @a else @b
|
||||||
|
@right = if @a.x > @b.x then @a else @b
|
||||||
|
@bottom = if @a.y < @b.y then @a else @b
|
||||||
|
@top = if @a.y > @b.y then @a else @b
|
||||||
|
|
||||||
|
y: (x) ->
|
||||||
|
(@slope * x) + @y0
|
||||||
|
|
||||||
|
x: (y) ->
|
||||||
|
(y - @y0) / @slope
|
||||||
|
|
||||||
|
intersectsLineSegment: (lineSegment) ->
|
||||||
|
if lineSegment.slope is @slope
|
||||||
|
if lineSegment.y0 is @y0
|
||||||
|
if lineSegment.left.x is @left.x or lineSegment.left.x is @right.x or lineSegment.right.x is @right.x or lineSegment.right.x is @left.x
|
||||||
|
# segments are of the same line with shared start and/or end points
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
[left, right] = if lineSegment.left.x < @left.x then [lineSegment, @] else [@, lineSegment]
|
||||||
|
if left.right.x > right.left.x
|
||||||
|
# segments are of the same line and one is contained within the other
|
||||||
|
return true
|
||||||
|
else if Math.abs(@slope) isnt Infinity and Math.abs(lineSegment.slope) isnt Infinity
|
||||||
|
x = (lineSegment.y0 - @y0) / (@slope - lineSegment.slope)
|
||||||
|
if x >= @left.x and x <= @right.x and x >= lineSegment.left.x and x <= lineSegment.right.x
|
||||||
|
return true
|
||||||
|
else if Math.abs(@slope) isnt Infinity or Math.abs(lineSegment.slope) isnt Infinity
|
||||||
|
[vertical, nonvertical] = if Math.abs(@slope) isnt Infinity then [lineSegment, @] else [@, lineSegment]
|
||||||
|
x = vertical.a.x
|
||||||
|
bottom = vertical.bottom.y
|
||||||
|
top = vertical.top.y
|
||||||
|
y = nonvertical.y(x)
|
||||||
|
left = nonvertical.left.x
|
||||||
|
right = nonvertical.right.x
|
||||||
|
if y >= bottom and y <= top and x >= left and x <= right
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
pointOnLine: (point, segment=true) ->
|
||||||
|
if point.y is @y(point.x)
|
||||||
|
if segment
|
||||||
|
[littleY, bigY] = if @a.y < @b.y then [@a.y, @b.y] else [@b.y, @a.y]
|
||||||
|
if littleY <= point.y and bigY >= point.y
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
distanceSquaredToPoint: (point) ->
|
||||||
|
# http://stackoverflow.com/a/1501725/540620
|
||||||
|
return @a.distanceSquared point if @a.equals @b
|
||||||
|
res = Math.min point.distanceSquared(@a), point.distanceSquared(@b)
|
||||||
|
lineMagnitudeSquared = @a.distanceSquared @b
|
||||||
|
t = ((point.x - @a.x) * (@b.x - @a.x) + (point.y - @a.y) * (@b.y - @a.y)) / lineMagnitudeSquared
|
||||||
|
return @a.distanceSquared point if t < 0
|
||||||
|
return @b.distanceSquared point if t > 1
|
||||||
|
point.distanceSquared x: @a.x + t * (@b.x - @a.x), y: @a.y + t * (@b.y - @a.y)
|
||||||
|
|
||||||
|
distanceToPoint: (point) ->
|
||||||
|
Math.sqrt @distanceSquaredToPoint point
|
||||||
|
|
||||||
|
toString: ->
|
||||||
|
"lineSegment(a=#{@a}, b=#{@b}, slope=#{@slope}, y0=#{@y0}, left=#{@left}, right=#{@right}, bottom=#{@bottom}, top=#{@top})"
|
||||||
|
|
||||||
|
serialize: ->
|
||||||
|
{CN: @constructor.className, a: @a, b: @b}
|
||||||
|
|
||||||
|
@deserialize: (o, world, classMap) ->
|
||||||
|
new LineSegment o.a, o.b
|
||||||
|
|
||||||
|
serializeForAether: -> @serialize()
|
||||||
|
@deserializeFromAether: (o) -> @deserialize o
|
||||||
|
|
||||||
|
module.exports = LineSegment
|
|
@ -1,14 +1,16 @@
|
||||||
Vector = require './vector'
|
Vector = require './vector'
|
||||||
|
LineSegment = require './line_segment'
|
||||||
|
|
||||||
class Rectangle
|
class Rectangle
|
||||||
@className: 'Rectangle'
|
@className: 'Rectangle'
|
||||||
# Class methods for nondestructively operating
|
# Class methods for nondestructively operating - TODO: add rotate
|
||||||
for name in ['add', 'subtract', 'multiply', 'divide']
|
for name in ['add', 'subtract', 'multiply', 'divide']
|
||||||
do (name) ->
|
do (name) ->
|
||||||
Rectangle[name] = (a, b) ->
|
Rectangle[name] = (a, b) ->
|
||||||
a.copy()[name](b)
|
a.copy()[name](b)
|
||||||
|
|
||||||
apiProperties: ['x', 'y', 'width', 'height', 'rotation', 'getPos', 'vertices', 'touchesRect', 'touchesPoint', 'distanceToPoint', 'containsPoint', 'copy']
|
isRectangle: true
|
||||||
|
apiProperties: ['x', 'y', 'width', 'height', 'rotation', 'getPos', 'vertices', 'touchesRect', 'touchesPoint', 'distanceToPoint', 'distanceSquaredToPoint', 'distanceToRectangle', 'distanceSquaredToRectangle', 'distanceToEllipse', 'distanceSquaredToEllipse', 'distanceToShape', 'distanceSquaredToShape', 'containsPoint', 'copy', 'intersectsLineSegment', 'intersectsEllipse', 'intersectsRectangle', 'intersectsShape']
|
||||||
|
|
||||||
constructor: (@x=0, @y=0, @width=0, @height=0, @rotation=0) ->
|
constructor: (@x=0, @y=0, @width=0, @height=0, @rotation=0) ->
|
||||||
|
|
||||||
|
@ -28,6 +30,14 @@ class Rectangle
|
||||||
new Vector @x + (w2 * cos + h2 * sin), @y + (w2 * sin - h2 * cos)
|
new Vector @x + (w2 * cos + h2 * sin), @y + (w2 * sin - h2 * cos)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
lineSegments: ->
|
||||||
|
vertices = @vertices()
|
||||||
|
lineSegment0 = new LineSegment vertices[0], vertices[1]
|
||||||
|
lineSegment1 = new LineSegment vertices[1], vertices[2]
|
||||||
|
lineSegment2 = new LineSegment vertices[2], vertices[3]
|
||||||
|
lineSegment3 = new LineSegment vertices[3], vertices[0]
|
||||||
|
[lineSegment0, lineSegment1, lineSegment2, lineSegment3]
|
||||||
|
|
||||||
touchesRect: (other) ->
|
touchesRect: (other) ->
|
||||||
# Whether this rect shares part of any edge with other rect, for non-rotated, non-overlapping rectangles.
|
# Whether this rect shares part of any edge with other rect, for non-rotated, non-overlapping rectangles.
|
||||||
# I think it says kitty-corner rects touch, but I don't think I want that.
|
# I think it says kitty-corner rects touch, but I don't think I want that.
|
||||||
|
@ -62,25 +72,90 @@ class Rectangle
|
||||||
box
|
box
|
||||||
|
|
||||||
distanceToPoint: (p) ->
|
distanceToPoint: (p) ->
|
||||||
# Get p in rect's coordinate space, then operate in one quadrant
|
# Get p in rect's coordinate space, then operate in one quadrant.
|
||||||
p = Vector.subtract(p, @getPos()).rotate(-@rotation)
|
p = Vector.subtract(p, @getPos()).rotate(-@rotation)
|
||||||
dx = Math.max(Math.abs(p.x) - @width / 2, 0)
|
dx = Math.max(Math.abs(p.x) - @width / 2, 0)
|
||||||
dy = Math.max(Math.abs(p.y) - @height / 2, 0)
|
dy = Math.max(Math.abs(p.y) - @height / 2, 0)
|
||||||
Math.sqrt dx * dx + dy * dy
|
Math.sqrt dx * dx + dy * dy
|
||||||
|
|
||||||
distanceSquaredToPoint: (p) ->
|
distanceSquaredToPoint: (p) ->
|
||||||
# Doesn't handle rotation; just supposed to be faster than distanceToPoint
|
# Doesn't handle rotation; just supposed to be faster than distanceToPoint.
|
||||||
p = Vector.subtract(p, @getPos())
|
p = Vector.subtract(p, @getPos())
|
||||||
dx = Math.max(Math.abs(p.x) - @width / 2, 0)
|
dx = Math.max(Math.abs(p.x) - @width / 2, 0)
|
||||||
dy = Math.max(Math.abs(p.y) - @height / 2, 0)
|
dy = Math.max(Math.abs(p.y) - @height / 2, 0)
|
||||||
dx * dx + dy * dy
|
dx * dx + dy * dy
|
||||||
|
|
||||||
|
distanceToRectangle: (other) ->
|
||||||
|
Math.sqrt @distanceSquaredToRectangle other
|
||||||
|
|
||||||
|
distanceSquaredToRectangle: (other) ->
|
||||||
|
return 0 if @intersectsRectangle other
|
||||||
|
[firstVertices, secondVertices] = [@vertices(), other.vertices()]
|
||||||
|
[firstEdges, secondEdges] = [@lineSegments(), other.lineSegments()]
|
||||||
|
ans = Infinity
|
||||||
|
for i in [0 ... 4]
|
||||||
|
for j in [0 ... firstEdges.length]
|
||||||
|
ans = Math.min ans, firstEdges[j].distanceSquaredToPoint(secondVertices[i])
|
||||||
|
for j in [0 ... secondEdges.length]
|
||||||
|
ans = Math.min ans, secondEdges[j].distanceSquaredToPoint(firstVertices[i])
|
||||||
|
ans
|
||||||
|
|
||||||
|
distanceToEllipse: (ellipse) ->
|
||||||
|
Math.sqrt @distanceSquaredToEllipse ellipse
|
||||||
|
|
||||||
|
distanceSquaredToEllipse: (ellipse) ->
|
||||||
|
@distanceSquaredToRectangle ellipse.rectangle() # TODO: actually implement rectangle-ellipse distance
|
||||||
|
|
||||||
|
distanceToShape: (shape) ->
|
||||||
|
Math.sqrt @distanceSquaredToShape shape
|
||||||
|
|
||||||
|
distanceSquaredToShape: (shape) ->
|
||||||
|
if shape.isEllipse then @distanceSquaredToEllipse shape else @distanceSquaredToRectangle shape
|
||||||
|
|
||||||
containsPoint: (p, withRotation=true) ->
|
containsPoint: (p, withRotation=true) ->
|
||||||
if withRotation and @rotation
|
if withRotation and @rotation
|
||||||
not @distanceToPoint(p)
|
not @distanceToPoint(p)
|
||||||
else
|
else
|
||||||
@x - @width / 2 < p.x < @x + @width / 2 and @y - @height / 2 < p.y < @y + @height / 2
|
@x - @width / 2 < p.x < @x + @width / 2 and @y - @height / 2 < p.y < @y + @height / 2
|
||||||
|
|
||||||
|
intersectsLineSegment: (p1, p2) ->
|
||||||
|
[px1, py1, px2, py2] = [p1.x, p1.y, p2.x, p2.y]
|
||||||
|
m1 = (py1 - py2) / (px1 - px2)
|
||||||
|
b1 = py1 - (m1 * px1)
|
||||||
|
vertices = @vertices()
|
||||||
|
lineSegments = [[vertices[0], vertices[1]], [vertices[1], vertices[2]], [vertices[2], vertices[3]], [vertices[3], vertices[0]]]
|
||||||
|
for lineSegment in lineSegments
|
||||||
|
[px1, py1, px2, py2] = [p1.x, p1.y, p2.x, p2.y]
|
||||||
|
m2 = (py1 - py2) / (px1 - px2)
|
||||||
|
b2 = py1 - (m * px1)
|
||||||
|
if m1 isnt m2
|
||||||
|
m = m1 - m2
|
||||||
|
b = b2 - b1
|
||||||
|
x = b / m
|
||||||
|
[littleX, bigX] = if px1 < px2 then [px1, px2] else [px2, px1]
|
||||||
|
if x >= littleX and x <= bigX
|
||||||
|
y = (m1 * x) + b1
|
||||||
|
[littleY, bigY] = if py1 < py2 then [py1, py2] else [py2, py1]
|
||||||
|
if littleY <= solution and bigY >= solution
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
intersectsRectangle: (rectangle) ->
|
||||||
|
return true if @containsPoint rectangle.getPos()
|
||||||
|
for thisLineSegment in @lineSegments()
|
||||||
|
for thatLineSegment in rectangle.lineSegments()
|
||||||
|
if thisLineSegment.intersectsLineSegment(thatLineSegment)
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
intersectsEllipse: (ellipse) ->
|
||||||
|
return true if @containsPoint ellipse.getPos()
|
||||||
|
return true for lineSegment in @lineSegments() when ellipse.intersectsLineSegment lineSegment.a, lineSegment.b
|
||||||
|
false
|
||||||
|
|
||||||
|
intersectsShape: (shape) ->
|
||||||
|
if shape.isEllipse then @intersectsEllipse shape else @intersectsRectangle shape
|
||||||
|
|
||||||
subtract: (point) ->
|
subtract: (point) ->
|
||||||
@x -= point.x
|
@x -= point.x
|
||||||
@y -= point.y
|
@y -= point.y
|
||||||
|
@ -102,10 +177,10 @@ class Rectangle
|
||||||
@
|
@
|
||||||
|
|
||||||
isEmpty: () ->
|
isEmpty: () ->
|
||||||
@width == 0 and @height == 0
|
@width is 0 and @height is 0
|
||||||
|
|
||||||
invalid: () ->
|
invalid: () ->
|
||||||
return (@x == Infinity) || isNaN(@x) || @y == Infinity || isNaN(@y) || @width == Infinity || isNaN(@width) || @height == Infinity || isNaN(@height) || @rotation == Infinity || isNaN(@rotation)
|
return (@x is Infinity) || isNaN(@x) || @y is Infinity || isNaN(@y) || @width is Infinity || isNaN(@width) || @height is Infinity || isNaN(@height) || @rotation is Infinity || isNaN(@rotation)
|
||||||
|
|
||||||
toString: ->
|
toString: ->
|
||||||
return "{x: #{@x.toFixed(0)}, y: #{@y.toFixed(0)}, w: #{@width.toFixed(0)}, h: #{@height.toFixed(0)}, rot: #{@rotation.toFixed(3)}}"
|
return "{x: #{@x.toFixed(0)}, y: #{@y.toFixed(0)}, w: #{@width.toFixed(0)}, h: #{@height.toFixed(0)}, rot: #{@rotation.toFixed(3)}}"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
Vector = require './vector'
|
Vector = require './vector'
|
||||||
Rectangle = require './rectangle'
|
Rectangle = require './rectangle'
|
||||||
|
Ellipse = require './ellipse'
|
||||||
|
LineSegment = require './line_segment'
|
||||||
WorldFrame = require './world_frame'
|
WorldFrame = require './world_frame'
|
||||||
Thang = require './thang'
|
Thang = require './thang'
|
||||||
ThangState = require './thang_state'
|
ThangState = require './thang_state'
|
||||||
|
@ -21,7 +23,7 @@ module.exports = class World
|
||||||
apiProperties: ['age', 'dt']
|
apiProperties: ['age', 'dt']
|
||||||
constructor: (@userCodeMap, classMap) ->
|
constructor: (@userCodeMap, classMap) ->
|
||||||
# classMap is needed for deserializing Worlds, Thangs, and other classes
|
# classMap is needed for deserializing Worlds, Thangs, and other classes
|
||||||
@classMap = classMap ? {Vector: Vector, Rectangle: Rectangle, Thang: Thang}
|
@classMap = classMap ? {Vector: Vector, Rectangle: Rectangle, Thang: Thang, Ellipse: Ellipse, LineSegment: LineSegment}
|
||||||
Thang.resetThangIDs()
|
Thang.resetThangIDs()
|
||||||
|
|
||||||
@userCodeMap ?= {}
|
@userCodeMap ?= {}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
Vector = require './vector'
|
Vector = require './vector'
|
||||||
Rectangle = require './rectangle'
|
Rectangle = require './rectangle'
|
||||||
|
Ellipse = require './ellipse'
|
||||||
|
LineSegment = require './line_segment'
|
||||||
Grid = require './Grid'
|
Grid = require './Grid'
|
||||||
|
|
||||||
module.exports.typedArraySupport = typedArraySupport = Float32Array? # Not in IE until IE 10; we'll fall back to normal arrays
|
module.exports.typedArraySupport = typedArraySupport = Float32Array? # Not in IE until IE 10; we'll fall back to normal arrays
|
||||||
|
@ -36,7 +38,7 @@ module.exports.clone = clone = (obj, skipThangs=false) ->
|
||||||
flags += 'y' if obj.sticky?
|
flags += 'y' if obj.sticky?
|
||||||
return new RegExp(obj.source, flags)
|
return new RegExp(obj.source, flags)
|
||||||
|
|
||||||
if (obj instanceof Vector) or (obj instanceof Rectangle)
|
if (obj instanceof Vector) or (obj instanceof Rectangle) or (obj instanceof Ellipse) or (obj instanceof LineSegment)
|
||||||
return obj.copy()
|
return obj.copy()
|
||||||
|
|
||||||
if skipThangs and obj.isThang
|
if skipThangs and obj.isThang
|
||||||
|
|
|
@ -6,6 +6,8 @@ serializedClasses =
|
||||||
Thang: require 'lib/world/thang'
|
Thang: require 'lib/world/thang'
|
||||||
Vector: require 'lib/world/vector'
|
Vector: require 'lib/world/vector'
|
||||||
Rectangle: require 'lib/world/rectangle'
|
Rectangle: require 'lib/world/rectangle'
|
||||||
|
Ellipse: require 'lib/world/ellipse'
|
||||||
|
LineSegment: require 'lib/world/line_segment'
|
||||||
|
|
||||||
module.exports = class DebugView extends View
|
module.exports = class DebugView extends View
|
||||||
className: 'spell-debug-view'
|
className: 'spell-debug-view'
|
||||||
|
|
|
@ -14,7 +14,7 @@ describe 'Camera (Surface point of view)', ->
|
||||||
|
|
||||||
sup = cam.worldToSurface wop
|
sup = cam.worldToSurface wop
|
||||||
expect(sup.x).toBeCloseTo wop.x * Camera.PPM
|
expect(sup.x).toBeCloseTo wop.x * Camera.PPM
|
||||||
expect(sup.y).toBeCloseTo cam.surfaceHeight - (wop.y + wop.z * cam.z2y) * cam.y2x * Camera.PPM
|
expect(sup.y).toBeCloseTo -(wop.y + wop.z * cam.z2y) * cam.y2x * Camera.PPM
|
||||||
|
|
||||||
cap = cam.worldToCanvas wop
|
cap = cam.worldToCanvas wop
|
||||||
expect(cap.x).toBeCloseTo (sup.x - cam.surfaceViewport.x) * cam.zoom
|
expect(cap.x).toBeCloseTo (sup.x - cam.surfaceViewport.x) * cam.zoom
|
||||||
|
@ -83,47 +83,48 @@ describe 'Camera (Surface point of view)', ->
|
||||||
testAngles = [0, Math.PI / 4, null, Math.PI / 2]
|
testAngles = [0, Math.PI / 4, null, Math.PI / 2]
|
||||||
testFOVs = [Math.PI / 6, Math.PI / 3, Math.PI / 2, Math.PI]
|
testFOVs = [Math.PI / 6, Math.PI / 3, Math.PI / 2, Math.PI]
|
||||||
|
|
||||||
xit 'handles lots of different cases correctly', ->
|
it 'handles lots of different cases correctly', ->
|
||||||
for wop in testWops
|
for wop in testWops
|
||||||
for size in testCanvasSizes
|
for size in testCanvasSizes
|
||||||
for zoom in testZooms
|
for zoom in testZooms
|
||||||
for target in testZoomTargets
|
for target in testZoomTargets
|
||||||
for angle in testAngles
|
for angle in testAngles
|
||||||
for fov in testFOVs
|
for fov in testFOVs
|
||||||
cam = new Camera size.width, size.height, size.width * Camera.MPP, size.height * Camera.MPP, testLayer, zoom, null, angle, fov
|
cam = new Camera {attr: (attr) -> if 'attr' is 'width' then size.width else size.height}, angle, fov
|
||||||
checkCameraPos cam, wop
|
checkCameraPos cam, wop
|
||||||
cam.zoomTo target, zoom, 0
|
cam.zoomTo target, zoom, 0
|
||||||
checkConversionsFromWorldPos wop, cam
|
checkConversionsFromWorldPos wop, cam
|
||||||
checkCameraPos cam, wop
|
checkCameraPos cam, wop
|
||||||
|
|
||||||
it 'works at 90 degrees', ->
|
it 'works at 90 degrees', ->
|
||||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
cam = new Camera {attr: (attr) -> 100}, Math.PI / 2
|
||||||
expect(cam.x2y).toBeCloseTo 1
|
expect(cam.x2y).toBeCloseTo 1
|
||||||
expect(cam.x2z).toBeGreaterThan 9001
|
expect(cam.x2z).toBeGreaterThan 9001
|
||||||
expect(cam.z2y).toBeCloseTo 0
|
expect(cam.z2y).toBeCloseTo 0
|
||||||
|
|
||||||
it 'works at 0 degrees', ->
|
it 'works at 0 degrees', ->
|
||||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
cam = new Camera {attr: (attr) -> 100}, 0
|
||||||
expect(cam.x2z).toBeGreaterThan 9001
|
expect(cam.x2y).toBeGreaterThan 9001
|
||||||
expect(cam.x2y).toBeCloseTo 1
|
expect(cam.x2z).toBeCloseTo 1
|
||||||
expect(cam.z2y).toBeCloseTo 0
|
expect(cam.z2y).toBeGreaterThan 9001
|
||||||
|
|
||||||
it 'works at 45 degrees', ->
|
it 'works at 45 degrees', ->
|
||||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
cam = new Camera {attr: (attr) -> 100}, Math.PI / 4
|
||||||
expect(cam.x2y).toBeCloseTo 1
|
expect(cam.x2y).toBeCloseTo Math.sqrt(2)
|
||||||
expect(cam.x2z).toBeGreaterThan 9001
|
expect(cam.x2z).toBeCloseTo Math.sqrt(2)
|
||||||
expect(cam.z2y).toBeCloseTo 0
|
expect(cam.z2y).toBeCloseTo 1
|
||||||
|
|
||||||
xit 'works at default angle of asin(0.75) ~= 48.9 degrees', ->
|
it 'works at default angle of asin(0.75) ~= 48.9 degrees', ->
|
||||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
cam = new Camera {attr: (attr) -> 100}, null
|
||||||
angle = 1 / Math.cos angle
|
angle = Math.asin(3 / 4)
|
||||||
expect(cam.angle).toBeCloseTo angle
|
expect(cam.angle).toBeCloseTo angle
|
||||||
expect(cam.x2y).toBeCloseTo 1
|
expect(cam.x2y).toBeCloseTo 4 / 3
|
||||||
expect(cam.x2z).toBeGreaterThan 9001
|
expect(cam.x2z).toBeCloseTo 1 / Math.cos angle
|
||||||
expect(cam.z2y).toBeCloseTo 0
|
expect(cam.z2y).toBeCloseTo (4 / 3) * Math.cos angle
|
||||||
|
|
||||||
xit 'works at 2x zoom, 90 degrees', ->
|
xit 'works at 2x zoom, 90 degrees', ->
|
||||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
cam = new Camera {attr: (attr) -> 100}, Math.PI / 2
|
||||||
|
cam.zoomTo null, 2, 0
|
||||||
checkCameraPos cam
|
checkCameraPos cam
|
||||||
wop = x: 5, y: 2.5, z: 7
|
wop = x: 5, y: 2.5, z: 7
|
||||||
cap = cam.worldToCanvas wop
|
cap = cam.worldToCanvas wop
|
||||||
|
@ -143,7 +144,8 @@ describe 'Camera (Surface point of view)', ->
|
||||||
expectPositionsEqual cap, {x: 0, y: 50}
|
expectPositionsEqual cap, {x: 0, y: 50}
|
||||||
|
|
||||||
xit 'works at 2x zoom, 30 degrees', ->
|
xit 'works at 2x zoom, 30 degrees', ->
|
||||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 2 * 100 * Camera.MPP
|
cam = new Camera {attr: (attr) -> 100}, Math.PI / 6
|
||||||
|
cam.zoomTo null, 2, 0
|
||||||
expect(cam.x2y).toBeCloseTo 1
|
expect(cam.x2y).toBeCloseTo 1
|
||||||
expect(cam.x2z).toBeGreaterThan 9001
|
expect(cam.x2z).toBeGreaterThan 9001
|
||||||
checkCameraPos cam
|
checkCameraPos cam
|
||||||
|
@ -164,15 +166,18 @@ describe 'Camera (Surface point of view)', ->
|
||||||
expectPositionsEqual cap, {x: 50, y: -100}
|
expectPositionsEqual cap, {x: 50, y: -100}
|
||||||
|
|
||||||
it 'works at 2x zoom, 60 degree hFOV', ->
|
it 'works at 2x zoom, 60 degree hFOV', ->
|
||||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
cam = new Camera {attr: (attr) -> 100}, null, Math.PI / 3
|
||||||
|
cam.zoomTo null, 2, 0
|
||||||
checkCameraPos cam
|
checkCameraPos cam
|
||||||
|
|
||||||
xit 'works at 2x zoom, 60 degree hFOV, 40 degree hFOV', ->
|
it 'works at 2x zoom, 60 degree hFOV, 40 degree vFOV', ->
|
||||||
cam = new Camera {attr: (x) -> x is 'height' ? 63.041494 : 100}, 100 * Camera.MPP, 63.041494 * Camera.MPP
|
cam = new Camera {attr: (attr) -> if attr is 'height' then 63.041494 else 100}, null, Math.PI / 3
|
||||||
|
cam.zoomTo null, 2, 0
|
||||||
checkCameraPos cam
|
checkCameraPos cam
|
||||||
|
|
||||||
xit 'works on a surface wider than it is tall, 30 degrees, default viewing upper left corner', ->
|
xit 'works at 2x zoom on a surface wider than it is tall, 30 degrees, default viewing upper left corner', ->
|
||||||
cam = new Camera {attr: (x) -> 100}, 200 * Camera.MPP, 2 * 50 * Camera.MPP
|
cam = new Camera {attr: (attr) -> 100}, Math.PI / 6 # 200 * Camera.MPP, 2 * 50 * Camera.MPP
|
||||||
|
cam.zoomTo null, 2, 0
|
||||||
checkCameraPos cam
|
checkCameraPos cam
|
||||||
expect(cam.zoom).toBeCloseTo 2
|
expect(cam.zoom).toBeCloseTo 2
|
||||||
wop = x: 5, y: 4, z: 6 * cam.y2z # like x: 5, y: 10 out of world width: 20, height: 10
|
wop = x: 5, y: 4, z: 6 * cam.y2z # like x: 5, y: 10 out of world width: 20, height: 10
|
||||||
|
|
99
test/app/lib/world/ellipse.spec.coffee
Normal file
99
test/app/lib/world/ellipse.spec.coffee
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
describe 'Ellipse', ->
|
||||||
|
Ellipse = require 'lib/world/ellipse'
|
||||||
|
Rectangle = require 'lib/world/rectangle'
|
||||||
|
Vector = require 'lib/world/vector'
|
||||||
|
|
||||||
|
#it 'contains its own center', ->
|
||||||
|
# ellipse = new Ellipse 0, 0, 10, 10
|
||||||
|
# expect(ellipse.containsPoint(new Vector 0, 0)).toBe true
|
||||||
|
#
|
||||||
|
#it 'contains a point when rotated', ->
|
||||||
|
# ellipse = new Ellipse 0, -20, 40, 40, 3 * Math.PI / 4
|
||||||
|
# p = new Vector 0, 2
|
||||||
|
# expect(ellipse.containsPoint(p, true)).toBe true
|
||||||
|
#
|
||||||
|
#it 'contains more points properly', ->
|
||||||
|
# # ellipse with y major axis, off-origin center, and 45 degree rotation
|
||||||
|
# ellipse = new Ellipse 1, 2, 4, 6, Math.PI / 4
|
||||||
|
# expect(ellipse.contains new Vector(1, 2)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(-1, 3)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(0, 4)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(1, 4)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(3, 0)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(1, 0)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(0, 1)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(-1, 2)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(2, 2)).toBe true
|
||||||
|
# expect(ellipse.contains new Vector(0, 0)).toBe false
|
||||||
|
# expect(ellipse.contains new Vector(0, 5)).toBe false
|
||||||
|
# expect(ellipse.contains new Vector(3, 4)).toBe false
|
||||||
|
# expect(ellipse.contains new Vector(4, 0)).toBe false
|
||||||
|
# expect(ellipse.contains new Vector(2, -1)).toBe false
|
||||||
|
# expect(ellipse.contains new Vector(0, -3)).toBe false
|
||||||
|
# expect(ellipse.contains new Vector(-2, -2)).toBe false
|
||||||
|
# expect(ellipse.contains new Vector(-2, 0)).toBe false
|
||||||
|
# expect(ellipse.contains new Vector(-2, 4)).toBe false
|
||||||
|
#
|
||||||
|
#it 'correctly calculates distance to a faraway point', ->
|
||||||
|
# ellipse = new Ellipse 100, 50, 20, 40
|
||||||
|
# p = new Vector 200, 300
|
||||||
|
# d = 10 * Math.sqrt(610)
|
||||||
|
# expect(ellipse.distanceToPoint(p)).toBeCloseTo d
|
||||||
|
# ellipse.rotation = Math.PI / 2
|
||||||
|
# d = 80 * Math.sqrt(10)
|
||||||
|
# expect(ellipse.distanceToPoint(p)).toBeCloseTo d
|
||||||
|
#
|
||||||
|
#it 'does not modify itself or target Vector when calculating distance', ->
|
||||||
|
# ellipse = new Ellipse -100, -200, 1, 100
|
||||||
|
# ellipse2 = ellipse.copy()
|
||||||
|
# p = new Vector -100.25, -101
|
||||||
|
# p2 = p.copy()
|
||||||
|
# ellipse.distanceToPoint(p)
|
||||||
|
# expect(p.x).toEqual p2.x
|
||||||
|
# expect(p.y).toEqual p2.y
|
||||||
|
# expect(ellipse.x).toEqual ellipse2.x
|
||||||
|
# expect(ellipse.y).toEqual ellipse2.y
|
||||||
|
# expect(ellipse.width).toEqual ellipse2.width
|
||||||
|
# expect(ellipse.height).toEqual ellipse2.height
|
||||||
|
# expect(ellipse.rotation).toEqual ellipse2.rotation
|
||||||
|
#
|
||||||
|
#it 'correctly calculates distance to contained point', ->
|
||||||
|
# ellipse = new Ellipse -100, -200, 1, 100
|
||||||
|
# ellipse2 = ellipse.copy()
|
||||||
|
# p = new Vector -100.25, -160
|
||||||
|
# p2 = p.copy()
|
||||||
|
# expect(ellipse.distanceToPoint(p)).toBe 0
|
||||||
|
# ellipse.rotation = 0.00000001 * Math.PI
|
||||||
|
# expect(ellipse.distanceToPoint(p)).toBe 0
|
||||||
|
#
|
||||||
|
#it 'AABB works when not rotated', ->
|
||||||
|
# ellipse = new Ellipse 10, 20, 30, 40
|
||||||
|
# rect = new Rectangle 10, 20, 30, 40
|
||||||
|
# aabb1 = ellipse.axisAlignedBoundingBox()
|
||||||
|
# aabb2 = ellipse.axisAlignedBoundingBox()
|
||||||
|
# for prop in ['x', 'y', 'width', 'height']
|
||||||
|
# expect(aabb1[prop]).toBe aabb2[prop]
|
||||||
|
#
|
||||||
|
#it 'AABB works when rotated', ->
|
||||||
|
# ellipse = new Ellipse 10, 20, 30, 40, Math.PI / 3
|
||||||
|
# rect = new Rectangle 10, 20, 30, 40, Math.PI / 3
|
||||||
|
# aabb1 = ellipse.axisAlignedBoundingBox()
|
||||||
|
# aabb2 = ellipse.axisAlignedBoundingBox()
|
||||||
|
# for prop in ['x', 'y', 'width', 'height']
|
||||||
|
# expect(aabb1[prop]).toBe aabb2[prop]
|
||||||
|
#
|
||||||
|
#it 'calculates ellipse intersections properly', ->
|
||||||
|
# # ellipse with y major axis, off-origin center, and 45 degree rotation
|
||||||
|
# ellipse = new Ellipse 1, 2, 4, 6, Math.PI / 4
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(0, 0, 2, 2, 0)).toBe true
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(0, -1, 2, 3, 0)).toBe true
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(-1, -0.5, 2 * Math.SQRT2, 2 * Math.SQRT2, Math.PI / 4)).toBe true
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(-1, -0.5, 2 * Math.SQRT2, 2 * Math.SQRT2, 0)).toBe true
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(-1, -1, 2 * Math.SQRT2, 2 * Math.SQRT2, 0)).toBe true
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(-1, -1, 2 * Math.SQRT2, 2 * Math.SQRT2, Math.PI / 4)).toBe false
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(-2, -2, 2, 2, 0)).toBe false
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(-Math.SQRT2 / 2, -Math.SQRT2 / 2, Math.SQRT2, Math.SQRT2, 0)).toBe false
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(-Math.SQRT2 / 2, -Math.SQRT2 / 2, Math.SQRT2, Math.SQRT2, Math.PI / 4)).toBe false
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(-2, 0, 2, 2, 0)).toBe false
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(0, -2, 2, 2, 0)).toBe false
|
||||||
|
# expect(ellipse.intersectsShape new Rectangle(1, 2, 1, 1, 0)).toBe true
|
51
test/app/lib/world/line_segment.spec.coffee
Normal file
51
test/app/lib/world/line_segment.spec.coffee
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
describe 'LineSegment', ->
|
||||||
|
LineSegment = require 'lib/world/line_segment'
|
||||||
|
Vector = require 'lib/world/vector'
|
||||||
|
|
||||||
|
v00 = new Vector(0, 0)
|
||||||
|
v11 = new Vector(1, 1)
|
||||||
|
v22 = new Vector(2, 2)
|
||||||
|
v34 = new Vector(3, 4)
|
||||||
|
v04 = new Vector(0, 4)
|
||||||
|
v30 = new Vector(3, 0)
|
||||||
|
vneg = new Vector(-1, -1)
|
||||||
|
|
||||||
|
it 'intersects itself', ->
|
||||||
|
lineSegment = new LineSegment v00, v34
|
||||||
|
expect(lineSegment.intersectsLineSegment lineSegment).toBe true
|
||||||
|
|
||||||
|
it 'intersects other segments properly', ->
|
||||||
|
l1 = new LineSegment v00, v34
|
||||||
|
l2 = new LineSegment v04, v30
|
||||||
|
l3 = new LineSegment v00, v11
|
||||||
|
expect(l1.intersectsLineSegment l2).toBe true
|
||||||
|
expect(l2.intersectsLineSegment l1).toBe true
|
||||||
|
expect(l1.intersectsLineSegment l3).toBe true
|
||||||
|
expect(l3.intersectsLineSegment l1).toBe true
|
||||||
|
expect(l2.intersectsLineSegment l3).toBe false
|
||||||
|
expect(l3.intersectsLineSegment l2).toBe false
|
||||||
|
|
||||||
|
it 'can tell when a point is on a line or segment', ->
|
||||||
|
lineSegment = new LineSegment v00, v11
|
||||||
|
expect(lineSegment.pointOnLine v22, false).toBe true
|
||||||
|
#expect(lineSegment.pointOnLine v22, true).toBe false
|
||||||
|
#expect(lineSegment.pointOnLine v00, false).toBe true
|
||||||
|
#expect(lineSegment.pointOnLine v00, true).toBe true
|
||||||
|
#expect(lineSegment.pointOnLine v11, true).toBe true
|
||||||
|
#expect(lineSegment.pointOnLine v11, false).toBe true
|
||||||
|
#expect(lineSegment.pointOnLine v34, false).toBe false
|
||||||
|
#expect(lineSegment.pointOnLine v34, true).toBe false
|
||||||
|
|
||||||
|
it 'correctly calculates distance to points', ->
|
||||||
|
lineSegment = new LineSegment v00, v11
|
||||||
|
expect(lineSegment.distanceToPoint v00).toBe 0
|
||||||
|
expect(lineSegment.distanceToPoint v11).toBe 0
|
||||||
|
expect(lineSegment.distanceToPoint v22).toBeCloseTo Math.SQRT2
|
||||||
|
expect(lineSegment.distanceToPoint v34).toBeCloseTo Math.sqrt(2 * 2 + 3 * 3)
|
||||||
|
expect(lineSegment.distanceToPoint v04).toBeCloseTo Math.sqrt(1 * 1 + 3 * 3)
|
||||||
|
expect(lineSegment.distanceToPoint v30).toBeCloseTo Math.sqrt(2 * 2 + 1 * 1)
|
||||||
|
expect(lineSegment.distanceToPoint vneg).toBeCloseTo Math.SQRT2
|
||||||
|
|
||||||
|
nullSegment = new LineSegment v11, v11
|
||||||
|
expect(lineSegment.distanceToPoint v11).toBe 0
|
||||||
|
expect(lineSegment.distanceToPoint v22).toBeCloseTo Math.SQRT2
|
|
@ -1,6 +1,7 @@
|
||||||
describe 'Rectangle', ->
|
describe 'Rectangle', ->
|
||||||
Rectangle = require 'lib/world/rectangle'
|
Rectangle = require 'lib/world/rectangle'
|
||||||
Vector = require 'lib/world/vector'
|
Vector = require 'lib/world/vector'
|
||||||
|
Ellipse = require 'lib/world/ellipse'
|
||||||
|
|
||||||
it 'contains its own center', ->
|
it 'contains its own center', ->
|
||||||
rect = new Rectangle 0, 0, 10, 10
|
rect = new Rectangle 0, 0, 10, 10
|
||||||
|
@ -43,6 +44,13 @@ describe 'Rectangle', ->
|
||||||
rect.rotation = 0.00000001 * Math.PI
|
rect.rotation = 0.00000001 * Math.PI
|
||||||
expect(rect.distanceToPoint(p)).toBe 0
|
expect(rect.distanceToPoint(p)).toBe 0
|
||||||
|
|
||||||
|
it 'correctly calculates distance to other rectangles', ->
|
||||||
|
expect(new Rectangle(0, 0, 4, 4, Math.PI / 4).distanceToRectangle(new Rectangle(4, -4, 2, 2, 0))).toBeCloseTo 2.2426
|
||||||
|
expect(new Rectangle(0, 0, 3, 3, 0).distanceToRectangle(new Rectangle(0, 0, 2, 2, 0))).toBe 0
|
||||||
|
expect(new Rectangle(0, 0, 3, 3, 0).distanceToRectangle(new Rectangle(0, 0, 2.5, 2.5, Math.PI / 4))).toBe 0
|
||||||
|
expect(new Rectangle(0, 0, 4, 4, 0).distanceToRectangle(new Rectangle(4, 2, 2, 2, 0))).toBe 1
|
||||||
|
expect(new Rectangle(0, 0, 4, 4, 0).distanceToRectangle(new Rectangle(4, 2, 2, 2, Math.PI / 4))).toBeCloseTo 2 - Math.SQRT2
|
||||||
|
|
||||||
it 'has predictable vertices', ->
|
it 'has predictable vertices', ->
|
||||||
rect = new Rectangle 50, 50, 100, 100
|
rect = new Rectangle 50, 50, 100, 100
|
||||||
v = rect.vertices()
|
v = rect.vertices()
|
||||||
|
@ -79,3 +87,22 @@ describe 'Rectangle', ->
|
||||||
aabb = rect.axisAlignedBoundingBox()
|
aabb = rect.axisAlignedBoundingBox()
|
||||||
for prop in ['x', 'y', 'width', 'height']
|
for prop in ['x', 'y', 'width', 'height']
|
||||||
expect(rect[prop]).toBe aabb[prop]
|
expect(rect[prop]).toBe aabb[prop]
|
||||||
|
|
||||||
|
it 'calculates rectangle intersections properly', ->
|
||||||
|
rect = new Rectangle 1, 1, 2, 2, 0
|
||||||
|
expect(rect.intersectsShape new Rectangle(3, 1, 2, 2, 0)).toBe true
|
||||||
|
expect(rect.intersectsShape new Rectangle(3, 3, 2, 2, 0)).toBe true
|
||||||
|
expect(rect.intersectsShape new Rectangle(1, 1, 2, 2, 0)).toBe true
|
||||||
|
expect(rect.intersectsShape new Rectangle(1, 1, Math.SQRT1_2, Math.SQRT1_2, Math.PI / 4)).toBe true
|
||||||
|
expect(rect.intersectsShape new Rectangle(4, 1, 2, 2, 0)).toBe false
|
||||||
|
expect(rect.intersectsShape new Rectangle(3, 4, 2, 2, 0)).toBe false
|
||||||
|
expect(rect.intersectsShape new Rectangle(1, 4, 2 * Math.SQRT1_2, 2 * Math.SQRT1_2, Math.PI / 4)).toBe false
|
||||||
|
expect(rect.intersectsShape new Rectangle(3, 1, 2, 2, Math.PI / 4)).toBe true
|
||||||
|
expect(rect.intersectsShape new Rectangle(1, 2, 2 * Math.SQRT2, 2 * Math.SQRT2, Math.PI / 4)).toBe true
|
||||||
|
|
||||||
|
it 'calculates ellipse intersections properly', ->
|
||||||
|
rect = new Rectangle 1, 1, 2, 2, 0
|
||||||
|
expect(rect.intersectsShape new Ellipse(1, 1, Math.SQRT1_2, Math.SQRT1_2, Math.PI / 4)).toBe true
|
||||||
|
expect(rect.intersectsShape new Ellipse(4, 1, 2, 2, 0)).toBe false
|
||||||
|
expect(rect.intersectsShape new Ellipse(3, 4, 2, 2, 0)).toBe false
|
||||||
|
expect(rect.intersectsShape new Ellipse(1, 4, 2 * Math.SQRT1_2, 2 * Math.SQRT1_2, Math.PI / 4)).toBe false
|
||||||
|
|
Loading…
Reference in a new issue