case class Point( x: Double, y: Double ): def +(that: Point) = Point(this.x + that.x, this.y + that.y) def unary_- : Point = Point(-x,-y) def -(that: Point) = this + (- that) def *(scalar: Double) = Point(x*scalar,y*scalar) def magnitude: Double = Math.sqrt(Math.pow(x,2) + Math.pow(y,2)) def phi: Double = Math.atan2(y,x) def normalized: Point = Point(Math.cos(phi),Math.sin(phi)) object Point: def distance(a: Point, b: Point): Double = (a - b).magnitude case class Path( color: String, width: Int, points: Vector[Point] ): def append(x: Double, y: Double): Path = { if (points.length > 1 && Point.distance(points.last,points.init.last) <= width * 2) copy(points = points.init :+ Point(x,y)) else copy(points = points :+ Point(x,y)) } val scale = 0.5 lazy val controlPoints: Seq[Point] = (for (i <- 0 until points.length) yield { if (i == 0) { val p1 = points(i) val p2 = points(i+1) val tangent = p2 - p1; val q1 = p1 + tangent * scale; Seq(p1,q1) } else if (i == points.length - 1) { val p0 = points(i - 1) val p1 = points(i) val tangent = (p1 - p0); val q0 = p1 - tangent * scale; Seq(q0,p1) } else { val p0 = points(i - 1) val p1 = points(i) val p2 = points(i + 1) val tangent = (p2 - p0).normalized; val q0 = p1 - tangent * scale * (p1 - p0).magnitude; val q1 = p1 + tangent * scale * (p2 - p1).magnitude; Seq(q0,p1,q1) } }).flatten def hits(x: Double, y: Double): Boolean = points.exists(p => Point.distance(p,Point(x,y)) <= width) def isEmpty: Boolean = points.length < 2