lib/scenic/primitive/quad.ex

#
#  Created by Boyd Multerer on 2017-05-06.
#  Copyright © 2017-2021 Kry10 Limited. All rights reserved.
#

defmodule Scenic.Primitive.Quad do
  @moduledoc """
  Draw a quad on the screen.

  ## Data

  `{point_a, point_b, point_c, point_d}`

  The data for a line is a tuple containing four points.
  * `point_a` - position to start drawing from
  * `point_b` - position to draw to
  * `point_c` - position to draw to
  * `point_d` - position to draw to

  ## Styles

  This primitive recognizes the following styles
  * [`hidden`](Scenic.Primitive.Style.Hidden.html) - show or hide the primitive
  * [`scissor`](Scenic.Primitive.Style.Scissor.html) - "scissor rectangle" that drawing will be clipped to.
  * [`fill`](Scenic.Primitive.Style.Fill.html) - fill in the area of the primitive
  * [`stroke`](Scenic.Primitive.Style.Stroke.html) - stroke the outline of the primitive. In this case, only the curvy part.
  * [`join`](Scenic.Primitive.Style.Join.html) - control how segments are joined.
  * [`miter_limit`](Scenic.Primitive.Style.MiterLimit.html) - control how segments are joined.

  ## Usage

  You should add/modify primitives via the helper functions in
  [`Scenic.Primitives`](Scenic.Primitives.html#quad/3)

  ```elixir
  graph
    |> quad(
      {{10, 0}, {20, 40}, {17, 50}, {0, 10}},
      stroke: {1, :yellow}
    )
  ```
  """

  use Scenic.Primitive
  alias Scenic.Script
  alias Scenic.Primitive
  alias Scenic.Primitive.Style
  alias Scenic.Primitive.Triangle

  @type t :: {
          {x0 :: number, y0 :: number},
          {x1 :: number, y1 :: number},
          {x2 :: number, y2 :: number},
          {x3 :: number, y3 :: number}
        }
  @type styles_t :: [
          :hidden | :scissor | :fill | :stroke_width | :stroke_fill | :join | :miter_limit
        ]

  @styles [:hidden, :scissor, :fill, :stroke_width, :stroke_fill, :join, :miter_limit]

  @impl Primitive
  @spec validate(t()) :: {:ok, t()} | {:error, String.t()}
  def validate({{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}} = data)
      when is_number(x0) and is_number(y0) and is_number(x1) and is_number(y1) and
             is_number(x2) and is_number(y2) and is_number(x3) and is_number(y3) do
    {:ok, data}
  end

  def validate(data) do
    {
      :error,
      """
      #{IO.ANSI.red()}Invalid Quad specification
      Received: #{inspect(data)}
      #{IO.ANSI.yellow()}
      The data for a Rectangle is {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}
      Each x/y pair represents a corner in the quad.#{IO.ANSI.default_color()}
      """
    }
  end

  # --------------------------------------------------------
  @doc """
  Returns a list of styles recognized by this primitive.
  """
  @impl Primitive
  @spec valid_styles() :: styles_t()
  def valid_styles(), do: @styles

  # --------------------------------------------------------
  @doc """
  Compile the data for this primitive into a mini script. This can be combined with others to
  generate a larger script and is called when a graph is compiled.
  """
  @impl Primitive
  @spec compile(primitive :: Primitive.t(), styles :: Style.t()) :: Script.t()
  def compile(
        %Primitive{module: __MODULE__, data: {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}},
        styles
      ) do
    Script.draw_quad([], x0, y0, x1, y1, x2, y2, x3, y3, Script.draw_flag(styles))
  end

  # --------------------------------------------------------
  def default_pin(data)

  def default_pin({{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}) do
    {
      (x0 + x1 + x2 + x3) / 4,
      (y0 + y1 + y2 + y3) / 4
    }
  end

  # # ------------------------------------
  # def expand({p0, p1, p2, p3}, width) do
  #   # account for the winding of quad - assumes convex, which is checked above
  #   cross =
  #     Math.Vector2.cross(
  #       Math.Vector2.sub(p1, p0),
  #       Math.Vector2.sub(p3, p0)
  #     )

  #   width =
  #     if cross < 0 do
  #       -width
  #     else
  #       width
  #     end

  #   # find the new parallel lines
  #   l01 = Math.Line.parallel({p0, p1}, width)
  #   l12 = Math.Line.parallel({p1, p2}, width)
  #   l23 = Math.Line.parallel({p2, p3}, width)
  #   l30 = Math.Line.parallel({p3, p0}, width)

  #   # calc the new poins from the intersections of the lines
  #   p0 = Math.Line.intersection(l30, l01)
  #   p1 = Math.Line.intersection(l01, l12)
  #   p2 = Math.Line.intersection(l12, l23)
  #   p3 = Math.Line.intersection(l23, l30)

  #   # return the expanded quad
  #   {p0, p1, p2, p3}
  # end

  # --------------------------------------------------------
  def contains_point?({p0, p1, p2, p3}, px) do
    # assumes convex, which is verified above
    Triangle.contains_point?({p0, p1, p2}, px) || Triangle.contains_point?({p1, p2, p3}, px)
  end

  # --------------------------------------------------------
  @doc false
  def default_pin({{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}, _styles) do
    {
      (x0 + x1 + x2 + x3) / 4,
      (y0 + y1 + y2 + y3) / 4
    }
  end
end