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 }