Skip to content

Commit

Permalink
Merge pull request #522 from JD557/optimize-polygon-rasterization
Browse files Browse the repository at this point in the history
Optimize polygon rasterization
  • Loading branch information
JD557 committed Sep 8, 2024
2 parents 6fe0c34 + d3365c2 commit 5992517
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,29 @@ final case class AxisAlignedBoundingBox(
x <- (x1 until x2).iterator
} f(x, y)
}

object AxisAlignedBoundingBox {

/** Mutable Builder for AABBs.
*/
final class Builder() {
private var x1: Int = Int.MaxValue
private var y1: Int = Int.MaxValue
private var x2: Int = Int.MinValue
private var y2: Int = Int.MinValue

def add(x: Int, y: Int): this.type = {
if (x < x1) x1 = x
if (y < y1) y1 = y
if (x > x2) x2 = x
if (y > y2) y2 = y
this
}

def add(point: Shape.Point): this.type = add(point.x, point.y)

def result(): AxisAlignedBoundingBox =
if (x1 > x2 || y1 > y2) AxisAlignedBoundingBox(0, 0, 0, 0)
else AxisAlignedBoundingBox(x1, y1, x2 - x1, y2 - y1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,33 @@ package eu.joaocosta.minart.geometry
* @param vertices ordered sequence of vertices.
*/
final case class ConvexPolygon(vertices: Vector[Shape.Point]) extends Shape {
require(vertices.size >= 3, "A polygon needs at least 3 vertices")

val aabb: AxisAlignedBoundingBox = {
val x1 = vertices.iterator.minBy(_.x).x
val y1 = vertices.iterator.minBy(_.y).y
val x2 = vertices.iterator.maxBy(_.x).x
val y2 = vertices.iterator.maxBy(_.y).y
AxisAlignedBoundingBox(x1, y1, x2 - x1, y2 - y1)
val size = vertices.size
require(size >= 3, "A polygon needs at least 3 vertices")

lazy val aabb: AxisAlignedBoundingBox = {
val builder = AxisAlignedBoundingBox.Builder()
vertices.foreach(builder.add)
builder.result()
}

val knownFace: Option[Shape.Face] =
faceAt(aabb.centerX, aabb.centerY)
lazy val knownFace: Option[Shape.Face] =
faceAt(vertices.head)

private def edgeFunction(x1: Int, y1: Int, x2: Int, y2: Int, x3: Int, y3: Int): Int =
(x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)

private def edgeFunction(p1: Shape.Point, p2: Shape.Point, p3: Shape.Point): Int =
edgeFunction(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y)

private def rawWeights(x: Int, y: Int): Iterator[Int] =
(0 until vertices.size).iterator.map(idx =>
private def rawWeights(x: Int, y: Int): Iterator[Int] = {
(0 until size).iterator.map(idx =>
val current = vertices(idx)
val next = if (idx + 1 >= vertices.size) vertices(0) else vertices(idx + 1)
val next = if (idx + 1 >= size) vertices(0) else vertices(idx + 1)
edgeFunction(current.x, current.y, next.x, next.y, x, y)
)
}

private val maxWeight: Int =
private lazy val maxWeight: Int =
(vertices.tail)
.sliding(2)
.collect { case Vector(b, c) =>
Expand All @@ -47,14 +47,35 @@ final case class ConvexPolygon(vertices: Vector[Shape.Point]) extends Shape {
.sum

def faceAt(x: Int, y: Int): Option[Shape.Face] = {
val sides = rawWeights(x, y).filter(_ != 0).map(_ >= 0).distinct.toVector
if (sides.size == 1) {
if (sides.head) Shape.someFront else Shape.someBack
} else None
val it = rawWeights(x, y).filter(_ != 0).map(_ >= 0)
var res: Option[Shape.Face] = null
if (it.hasNext) {
var last = it.next()
while (it.hasNext && res != None) {
val value = it.next()
if (last != value) res = None
last = value
}
if (res == null) {
if (last) res = Shape.someFront
else res = Shape.someBack
}
}
if (res == null) None else res
}

override def contains(x: Int, y: Int): Boolean = {
rawWeights(x, y).filter(_ != 0).map(_ >= 0).distinct.size == 1
val it = rawWeights(x, y).filter(_ != 0).map(_ >= 0)
var res = true
if (it.hasNext) {
var last = it.next()
while (it.hasNext && res) {
val value = it.next()
if (last != value) res = false
last = value
}
}
res
}

/** Checks if this polygon contains another polygon.
Expand Down

0 comments on commit 5992517

Please sign in to comment.