mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 15:48:11 -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 = {
|
||||
"Thang": self.require('lib/world/thang'),
|
||||
"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.currentDebugWorldFrame = 0;
|
||||
|
|
|
@ -181,12 +181,11 @@ module.exports = class Mark extends CocoClass
|
|||
buildDebug: ->
|
||||
@mark = new createjs.Shape()
|
||||
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.graphics.beginFill '#abcdef'
|
||||
if @sprite.thang.shape in ['ellipsoid', 'disc']
|
||||
[w, h] = [Math.max(PX, w, h), Math.max(PX, w, h)]
|
||||
@mark.graphics.drawCircle 0, 0, w / 2
|
||||
@mark.graphics.drawEllipse -w / 2, -h / 2, w, h
|
||||
else
|
||||
@mark.graphics.drawRect -w / 2, -h / 2, w, h
|
||||
@mark.graphics.endFill()
|
||||
|
@ -259,7 +258,7 @@ module.exports = class Mark extends CocoClass
|
|||
|
||||
updateRotation: ->
|
||||
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: ->
|
||||
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'
|
||||
LineSegment = require './line_segment'
|
||||
|
||||
class Rectangle
|
||||
@className: 'Rectangle'
|
||||
# Class methods for nondestructively operating
|
||||
# Class methods for nondestructively operating - TODO: add rotate
|
||||
for name in ['add', 'subtract', 'multiply', 'divide']
|
||||
do (name) ->
|
||||
Rectangle[name] = (a, 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) ->
|
||||
|
||||
|
@ -28,6 +30,14 @@ class Rectangle
|
|||
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) ->
|
||||
# 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.
|
||||
|
@ -62,25 +72,90 @@ class Rectangle
|
|||
box
|
||||
|
||||
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)
|
||||
dx = Math.max(Math.abs(p.x) - @width / 2, 0)
|
||||
dy = Math.max(Math.abs(p.y) - @height / 2, 0)
|
||||
Math.sqrt dx * dx + dy * dy
|
||||
|
||||
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())
|
||||
dx = Math.max(Math.abs(p.x) - @width / 2, 0)
|
||||
dy = Math.max(Math.abs(p.y) - @height / 2, 0)
|
||||
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) ->
|
||||
if withRotation and @rotation
|
||||
not @distanceToPoint(p)
|
||||
else
|
||||
@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) ->
|
||||
@x -= point.x
|
||||
@y -= point.y
|
||||
|
@ -102,10 +177,10 @@ class Rectangle
|
|||
@
|
||||
|
||||
isEmpty: () ->
|
||||
@width == 0 and @height == 0
|
||||
@width is 0 and @height is 0
|
||||
|
||||
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: ->
|
||||
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'
|
||||
Rectangle = require './rectangle'
|
||||
Ellipse = require './ellipse'
|
||||
LineSegment = require './line_segment'
|
||||
WorldFrame = require './world_frame'
|
||||
Thang = require './thang'
|
||||
ThangState = require './thang_state'
|
||||
|
@ -21,7 +23,7 @@ module.exports = class World
|
|||
apiProperties: ['age', 'dt']
|
||||
constructor: (@userCodeMap, classMap) ->
|
||||
# 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()
|
||||
|
||||
@userCodeMap ?= {}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
Vector = require './vector'
|
||||
Rectangle = require './rectangle'
|
||||
Ellipse = require './ellipse'
|
||||
LineSegment = require './line_segment'
|
||||
Grid = require './Grid'
|
||||
|
||||
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?
|
||||
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()
|
||||
|
||||
if skipThangs and obj.isThang
|
||||
|
|
|
@ -6,6 +6,8 @@ serializedClasses =
|
|||
Thang: require 'lib/world/thang'
|
||||
Vector: require 'lib/world/vector'
|
||||
Rectangle: require 'lib/world/rectangle'
|
||||
Ellipse: require 'lib/world/ellipse'
|
||||
LineSegment: require 'lib/world/line_segment'
|
||||
|
||||
module.exports = class DebugView extends View
|
||||
className: 'spell-debug-view'
|
||||
|
|
|
@ -14,7 +14,7 @@ describe 'Camera (Surface point of view)', ->
|
|||
|
||||
sup = cam.worldToSurface wop
|
||||
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
|
||||
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]
|
||||
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 size in testCanvasSizes
|
||||
for zoom in testZooms
|
||||
for target in testZoomTargets
|
||||
for angle in testAngles
|
||||
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
|
||||
cam.zoomTo target, zoom, 0
|
||||
checkConversionsFromWorldPos wop, cam
|
||||
checkCameraPos cam, wop
|
||||
|
||||
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.x2z).toBeGreaterThan 9001
|
||||
expect(cam.z2y).toBeCloseTo 0
|
||||
|
||||
it 'works at 0 degrees', ->
|
||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
||||
expect(cam.x2z).toBeGreaterThan 9001
|
||||
expect(cam.x2y).toBeCloseTo 1
|
||||
expect(cam.z2y).toBeCloseTo 0
|
||||
cam = new Camera {attr: (attr) -> 100}, 0
|
||||
expect(cam.x2y).toBeGreaterThan 9001
|
||||
expect(cam.x2z).toBeCloseTo 1
|
||||
expect(cam.z2y).toBeGreaterThan 9001
|
||||
|
||||
it 'works at 45 degrees', ->
|
||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
||||
expect(cam.x2y).toBeCloseTo 1
|
||||
expect(cam.x2z).toBeGreaterThan 9001
|
||||
expect(cam.z2y).toBeCloseTo 0
|
||||
cam = new Camera {attr: (attr) -> 100}, Math.PI / 4
|
||||
expect(cam.x2y).toBeCloseTo Math.sqrt(2)
|
||||
expect(cam.x2z).toBeCloseTo Math.sqrt(2)
|
||||
expect(cam.z2y).toBeCloseTo 1
|
||||
|
||||
xit 'works at default angle of asin(0.75) ~= 48.9 degrees', ->
|
||||
cam = new Camera {attr: (x) -> 100}, 100 * Camera.MPP, 100 * Camera.MPP
|
||||
angle = 1 / Math.cos angle
|
||||
it 'works at default angle of asin(0.75) ~= 48.9 degrees', ->
|
||||
cam = new Camera {attr: (attr) -> 100}, null
|
||||
angle = Math.asin(3 / 4)
|
||||
expect(cam.angle).toBeCloseTo angle
|
||||
expect(cam.x2y).toBeCloseTo 1
|
||||
expect(cam.x2z).toBeGreaterThan 9001
|
||||
expect(cam.z2y).toBeCloseTo 0
|
||||
expect(cam.x2y).toBeCloseTo 4 / 3
|
||||
expect(cam.x2z).toBeCloseTo 1 / Math.cos angle
|
||||
expect(cam.z2y).toBeCloseTo (4 / 3) * Math.cos angle
|
||||
|
||||
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
|
||||
wop = x: 5, y: 2.5, z: 7
|
||||
cap = cam.worldToCanvas wop
|
||||
|
@ -143,7 +144,8 @@ describe 'Camera (Surface point of view)', ->
|
|||
expectPositionsEqual cap, {x: 0, y: 50}
|
||||
|
||||
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.x2z).toBeGreaterThan 9001
|
||||
checkCameraPos cam
|
||||
|
@ -164,15 +166,18 @@ describe 'Camera (Surface point of view)', ->
|
|||
expectPositionsEqual cap, {x: 50, y: -100}
|
||||
|
||||
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
|
||||
|
||||
xit 'works at 2x zoom, 60 degree hFOV, 40 degree hFOV', ->
|
||||
cam = new Camera {attr: (x) -> x is 'height' ? 63.041494 : 100}, 100 * Camera.MPP, 63.041494 * Camera.MPP
|
||||
it 'works at 2x zoom, 60 degree hFOV, 40 degree vFOV', ->
|
||||
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
|
||||
|
||||
xit 'works 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
|
||||
xit 'works at 2x zoom on a surface wider than it is tall, 30 degrees, default viewing upper left corner', ->
|
||||
cam = new Camera {attr: (attr) -> 100}, Math.PI / 6 # 200 * Camera.MPP, 2 * 50 * Camera.MPP
|
||||
cam.zoomTo null, 2, 0
|
||||
checkCameraPos cam
|
||||
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
|
||||
|
|
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', ->
|
||||
Rectangle = require 'lib/world/rectangle'
|
||||
Vector = require 'lib/world/vector'
|
||||
Ellipse = require 'lib/world/ellipse'
|
||||
|
||||
it 'contains its own center', ->
|
||||
rect = new Rectangle 0, 0, 10, 10
|
||||
|
@ -43,6 +44,13 @@ describe 'Rectangle', ->
|
|||
rect.rotation = 0.00000001 * Math.PI
|
||||
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', ->
|
||||
rect = new Rectangle 50, 50, 100, 100
|
||||
v = rect.vertices()
|
||||
|
@ -79,3 +87,22 @@ describe 'Rectangle', ->
|
|||
aabb = rect.axisAlignedBoundingBox()
|
||||
for prop in ['x', 'y', 'width', 'height']
|
||||
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