So close I can almost taste it.

This commit is contained in:
James Harton 2017-01-22 09:23:52 +13:00
parent 8dbe321e17
commit 92d64f4e99
3 changed files with 31 additions and 69 deletions

View file

@ -158,6 +158,9 @@ defmodule Vivid.Polygon do
|> init
end
def filled?(%Polygon{fill: fill}), do: fill
def fill(%Polygon{}=polygon, fill) when is_boolean(fill), do: %{polygon | fill: fill}
defp points_to_lines(lines, []) do
origin = lines |> List.last |> Line.termination
term = lines |> List.first |> Line.origin

View file

@ -1,5 +1,5 @@
defimpl Vivid.Rasterize, for: Vivid.Polygon do
alias Vivid.{Polygon, Rasterize, Point, Bounds, Line}
alias Vivid.{Polygon, Rasterize, Point, Bounds, Line, SLPFA}
require Integer
defmodule InvalidPolygonError do
@ -42,52 +42,9 @@ defimpl Vivid.Rasterize, for: Vivid.Polygon do
end
def rasterize(%Polygon{fill: true}=polygon, bounds) do
range = polygon
|> Bounds.bounds
|> y_range
lines = polygon
|> Polygon.to_lines
|> Enum.reject(&Line.horizontal?(&1))
points = Enum.reduce(range, MapSet.new, fn y, points ->
xs = lines
|> Stream.map(&Line.y_intersect(&1, y))
|> Stream.reject(&is_nil(&1))
|> Stream.map(&Point.x(&1))
|> Stream.map(&round(&1))
# |> Enum.dedup
|> Enum.sort
MapSet.new
|> reduce_x_fill([], xs, y)
|> Stream.filter(&Bounds.contains?(bounds, &1))
|> Enum.into(points)
end)
lines
|> Stream.flat_map(&Enum.to_list(&1))
|> Stream.map(&Point.round(&1))
|> Stream.filter(&Bounds.contains?(bounds, &1))
|> Enum.reduce(points, fn point, points -> MapSet.put(points, point) end)
end
defp y_range(bounds) do
y0 = bounds |> Bounds.min |> Point.y |> round
y1 = bounds |> Bounds.max |> Point.y |> round
if y1 > y0, do: y0..y1, else: y1..y0
end
defp reduce_x_fill(points, _lhs, [], _y), do: points
defp reduce_x_fill(points, lhs, rhs, y) when rem(length(lhs), 2) == 1 and rem(length(rhs), 2) == 1 do
[x0 | _] = lhs
[x1 | rhs] = rhs
points = Enum.reduce(x0..x1, points, fn x, points -> MapSet.put(points, Point.init(x, y)) end)
reduce_x_fill(points, [x1 | lhs], rhs, y)
end
defp reduce_x_fill(points, lhs, [x | rhs], y) do
reduce_x_fill(points, [x | lhs], rhs, y)
polygon
|> SLPFA.fill
|> Enum.filter(&Bounds.contains?(bounds, &1))
|> Enum.into(MapSet.new)
end
end

View file

@ -17,17 +17,17 @@ defmodule Vivid.SLPFA do
|> process_edge_table
end
defp process_edge_table([active0, active1 | edge_table]) do
scan_line = active0.y_min
active = [active0, active1]
defp process_edge_table([a0 | _]=edge_table) do
scan_line = a0.y_min
{active, edge_table} = update_active_list(scan_line, [], edge_table)
points = pixels_for_active_list(MapSet.new, active, scan_line)
process_edge_table(points, active, edge_table, scan_line + 1)
end
defp process_edge_table(points, _active, [], scan_line), do: points
defp process_edge_table(points, []=_active, _edge_table, scan_line), do: points
defp process_edge_table(points, active, edge_table, scan_line) do
active = update_active_list(scan_line, active, edge_table)
{active, edge_table} = update_active_list(scan_line, active, edge_table)
points = pixels_for_active_list(points, active, scan_line)
active = increment_active_edges(active)
process_edge_table(points, active, edge_table, scan_line + 1)
@ -64,10 +64,23 @@ defmodule Vivid.SLPFA do
end
defp update_active_list(scan_line, active, edge_table) do
active
|> Enum.reject(&remove_processed_edges(&1, scan_line))
|> add_active_edges(scan_line, edge_table)
{active, edge_table} = active
|> Stream.concat(edge_table)
|> Enum.reduce({[], []}, fn
%EdgeBucket{y_min: y_min, y_max: y_max}=edge, {active, edge_table} when y_min <= scan_line and y_max > scan_line ->
active = [edge | active]
{active, edge_table}
%EdgeBucket{y_min: y_min}=edge, {active, edge_table} when y_min >= scan_line ->
edge_table = [edge | edge_table]
{active, edge_table}
_edge_bucket, {active, edge_table} ->
{active, edge_table}
end)
active = active
|> Enum.sort(&sort_by_x_and_slope(&1, &2))
{active, edge_table}
end
defp sort_by_x_and_slope(%EdgeBucket{x: x0},
@ -75,22 +88,11 @@ defmodule Vivid.SLPFA do
when x0 < x1, do: true
defp sort_by_x_and_slope(%EdgeBucket{x: x0},
%EdgeBucket{x: x1})
when x1 > x0, do: false
when x0 > x1, do: false
defp sort_by_x_and_slope(%EdgeBucket{distance_x: dx0, distance_y: dy0},
%EdgeBucket{distance_x: dx1, distance_y: dy1}),
do: (dx0 / dy0) < (dx1 / dy1)
defp add_active_edges(active, _scan_line, []), do: Enum.reverse(active)
defp add_active_edges(active, scan_line, [%EdgeBucket{y_min: y_min}=edge | edge_table]) when scan_line == y_min do
add_active_edges([edge | active], scan_line, edge_table)
end
defp add_active_edges(active, scan_line, [_ | edge_table]) do
add_active_edges(active, scan_line, edge_table)
end
defp remove_processed_edges(%EdgeBucket{y_max: y}, scan_line) when y == scan_line, do: true
defp remove_processed_edges(_edge_bucket, _scan_line), do: false
defp create_edge_table(vertices) do
vertices
|> Stream.with_index