All Downloads are FREE. Search and download functionalities are using the official Maven repository.

doodle.core.BoundingBox.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Creative Scala
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package doodle
package core

/** A [[doodle.core.BoundingBox]] represents a bounding box around an picture.
  *
  * A bounding box also defines a local coordinate system for a picture. The
  * bounding box must contain the origin of the coordinate system. However the
  * origin need not be centered within the box.
  *
  * No particular guarantees are made about the tightness of the bounding box,
  * though it can assumed to be reasonably tight.
  */
final case class BoundingBox(
    left: Double,
    top: Double,
    right: Double,
    bottom: Double
) {
  def width: Double = right - left
  def height: Double = top - bottom

  def on(that: BoundingBox): BoundingBox =
    BoundingBox(
      this.left min that.left,
      this.top max that.top,
      this.right max that.right,
      this.bottom min that.bottom
    )

  def beside(that: BoundingBox): BoundingBox =
    BoundingBox(
      -(this.width + that.width) / 2.0,
      this.top max that.top,
      (this.width + that.width) / 2.0,
      this.bottom min that.bottom
    )

  def above(that: BoundingBox): BoundingBox =
    BoundingBox(
      this.left min that.left,
      (this.height + that.height) / 2.0,
      this.right max that.right,
      -(this.height + that.height) / 2.0
    )

  /** Evaluate the landmark relative to the origin of this bounding box,
    * returning the location described by the landmark.
    */
  def eval(landmark: Landmark): Point = {
    Point(landmark.x.eval(left, right), landmark.y.eval(bottom, top))
  }

  def at(point: Point): BoundingBox = {
    val x = point.x
    val y = point.y

    val newLeft = (left + x) min 0
    val newTop = (top + y) max 0
    val newRight = (right + x) max 0
    val newBottom = (bottom + y) min 0

    BoundingBox(newLeft, newTop, newRight, newBottom)
  }

  def at(landmark: Landmark): BoundingBox =
    at(eval(landmark))

  def originAt(landmark: Landmark): BoundingBox =
    originAt(eval(landmark))

  def originAt(point: Point): BoundingBox = {
    // Vector maths to work out where the edges of the bounding box lie in
    // relation to the new origin.
    val newTopLeft = Point(left, top) - point
    val newBottomRight = Point(right, bottom) - point

    // Make sure the bounding box includes the origin
    val newLeft = newTopLeft.x min 0
    val newTop = newTopLeft.y max 0
    val newRight = newBottomRight.x max 0
    val newBottom = newBottomRight.y min 0

    BoundingBox(newLeft, newTop, newRight, newBottom)
  }

  /** Expand bounding box to enclose the given `Point`. */
  def enclose(toInclude: Point): BoundingBox =
    BoundingBox(
      left min toInclude.x,
      top max toInclude.y,
      right max toInclude.x,
      bottom min toInclude.y
    )

  /** Add `expansion` to all sides of this bounding box. */
  def expand(expansion: Double): BoundingBox =
    BoundingBox(
      this.left - expansion,
      this.top + expansion,
      this.right + expansion,
      this.bottom - expansion
    )

  def transform(tx: Transform): BoundingBox = {
    BoundingBox.empty
      .enclose(tx(Point(left, top)))
      .enclose(tx(Point(right, top)))
      .enclose(tx(Point(left, bottom)))
      .enclose(tx(Point(right, bottom)))
  }
}

object BoundingBox {
  val empty = BoundingBox(0, 0, 0, 0)

  /** Create a [[doodle.core.BoundingBox]] with the given width and height and
    * the origin centered within the box.
    */
  def centered(width: Double, height: Double): BoundingBox = {
    val w = width / 2.0
    val h = height / 2.0

    BoundingBox(-w, h, w, -h)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy