mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-25 08:38:09 -05:00
175 lines
7.8 KiB
CoffeeScript
175 lines
7.8 KiB
CoffeeScript
|
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
|