Newer
Older
fractals / src / Main.elm
@peter peter on 13 Oct 2019 5 KB Refactoring.
module Main exposing (..)

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Svg as S
import Svg.Attributes as SA


type alias Point =
    { x : Float, y : Float }


type alias Line =
    { start : Point, end : Point }


add a b =
    Point (a.x + b.x) (a.y + b.y)


scale s p =
    Point (s * p.x) (s * p.y)


diff start end =
    add end (scale -1 start)


midpoint s line =
    add line.start (scale s (diff line.start line.end))


rotateMinus90Deg p =
    Point p.y -p.x


tip tipHeight line =
    diff line.start line.end
        |> rotateMinus90Deg
        |> scale ((sqrt 3 / 6) * tipHeight)
        |> add (midpoint 0.5 line)


kochMidPoints tipHeight gapWidth line =
    [ line.start
    , midpoint (0.5 - gapWidth / 2.0) line
    , tip tipHeight line
    , midpoint (0.5 + gapWidth / 2.0) line
    , line.end
    ]


pointListToLinesRec firstPoint remainingPoints lines =
    case remainingPoints of
        p :: ps ->
            pointListToLinesRec p ps (Line firstPoint p :: lines)

        [] ->
            lines


pointListToLines points =
    case points of
        p :: ps ->
            pointListToLinesRec p ps []

        [] ->
            []


kochDivide tipHeight gapWidth line =
    line |> kochMidPoints tipHeight gapWidth |> pointListToLines


kochIteration iterations model lineList =
    if iterations == 0 then
        lineList

    else
        List.concatMap (kochDivide model.tipHeight model.gapWidth) lineList
            |> kochIteration (iterations - 1) model


lineToSvg : Line -> S.Svg msg
lineToSvg line =
    S.line
        [ SA.x1 (String.fromFloat line.start.x)
        , SA.y1 (String.fromFloat line.start.y)
        , SA.x2 (String.fromFloat line.end.x)
        , SA.y2 (String.fromFloat line.end.y)
        ]
        []


baseHeight =
    200


startLine =
    Line (Point 50 baseHeight) (Point 350 baseHeight)


type alias Model =
    { gapWidth : Float
    , tipHeight : Float
    , iterations : Float
    }


type alias LabelledSliderParameters =
    { id : String
    , label : String
    , min : Float
    , max : Float
    , step : Float
    , instance : SliderInstance
    }


roundToPrecision : Int -> Float -> Float
roundToPrecision precision number =
    toFloat (round (number * toFloat (10 ^ precision))) / toFloat (10 ^ precision)


labelledSlider params modelValue =
    div [ class "labelled-slider" ]
        [ label [ for params.id ] [ text params.label ]
        , input
            [ id params.id
            , type_ "range"
            , Html.Attributes.min <| String.fromFloat <| params.min
            , Html.Attributes.max <| String.fromFloat <| params.max
            , step <| String.fromFloat <| params.step
            , value <| String.fromFloat <| modelValue
            , onInput (Change params.instance)
            ]
            []
        , span [] [ modelValue |> roundToPrecision 2 |> String.fromFloat |> text ]
        ]


view : Model -> Html Msg
view model =
    div [ class "tile" ]
        [ h2 [] [ text "Koch" ]
        , div []
            [ S.svg
                [ SA.width "400px"
                , SA.height "400px"
                , SA.style "fill: none; stroke: purple; stroke-width: 1;"
                ]
                ([ startLine ] |> kochIteration model.iterations model |> List.map lineToSvg)
            ]
        , div [ class "rowContainer" ]
            [ labelledSlider
                { id = "tipHeight"
                , label = "Tip: "
                , min = -3
                , max = 3
                , step = 0.01
                , instance = TipHeight
                }
                model.tipHeight
            , labelledSlider
                { id = "gapWidth"
                , label = "Gap: "
                , min = -3
                , max = 3
                , step = 0.01
                , instance = GapWidth
                }
                model.gapWidth
            , labelledSlider
                { id = "iterations"
                , label = "Iterations: "
                , min = 0
                , max = 6
                , step = 1
                , instance = Iterations
                }
                model.iterations
            ]
        ]


init : Model
init =
    { gapWidth = 1 / 3
    , tipHeight = 1
    , iterations = 5
    }


type SliderInstance
    = GapWidth
    | TipHeight
    | Iterations


type Msg
    = Change SliderInstance String


update : Msg -> Model -> Model
update msg model =
    case msg of
        Change GapWidth newWidth ->
            case String.toFloat newWidth of
                Just w ->
                    { model | gapWidth = w }

                Nothing ->
                    model

        Change TipHeight newHeight ->
            case String.toFloat newHeight of
                Just h ->
                    { model | tipHeight = h }

                Nothing ->
                    model

        Change Iterations newIterations ->
            case String.toFloat newIterations of
                Just i ->
                    { model | iterations = i }

                Nothing ->
                    model


main =
    Browser.sandbox { view = view, update = update, init = init }