<commit_before>module Camera exposing
    ( State
    , cameraDistance
    , encompass
    , finishDragging
    , initialState
    , isDragging
    , isMoving
    , lookAlong
    , nextFrame
    , perspectiveMatrix
    , setFrameSize
    , setMousePosition
    , setRedraws
    , startDragging
    , updateZoom
    , viewingMatrix
    )

import Math.Matrix4 as Mat4 exposing (Mat4)
import Math.Vector3 as Vec3 exposing (Vec3, vec3)
import Time exposing (Posix)


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


type State
    = State
        { size : { width : Float, height : Float }
        , origin : Position
        , cameraDistance : Float
        , fieldOfView : Float
        , dragging : Bool
        , moving : Bool
        , ndcPos : Position
        , shift : Vec3
        , rotation : Mat4
        , deltaRot : Mat4
        , milliSecsSinceMoved : Float
        }


initialState : State
initialState =
    State
        { size = { width = 0, height = 0 }
        , origin = { x = 0, y = 0 }
        , cameraDistance = 12
        , fieldOfView = 25
        , dragging = False
        , moving = False
        , ndcPos = { x = 0, y = 0 }
        , rotation = Mat4.identity
        , deltaRot = Mat4.identity
        , milliSecsSinceMoved = 0
        , shift = vec3 0 0 0
        }


nextFrame : Float -> State -> State
nextFrame timeInMilliSecs (State state) =
    if state.dragging then
        State
            { state
                | milliSecsSinceMoved =
                    state.milliSecsSinceMoved + timeInMilliSecs
            }

    else if state.moving then
        let
            rotation =
                orthonormalized <| Mat4.mul state.deltaRot state.rotation
        in
        State { state | rotation = rotation }

    else
        State state


positionToNdc : { x : Int, y : Int } -> State -> Position
positionToNdc pos (State state) =
    let
        xRelative =
            (toFloat pos.x - state.origin.x) / state.size.width

        yRelative =
            (toFloat pos.y - state.origin.y) / state.size.height
    in
    { x = 2 * xRelative - 1, y = 1 - 2 * yRelative }


setMousePosition : { x : Int, y : Int } -> Bool -> State -> State
setMousePosition pos alter (State state) =
    let
        ndcPos =
            positionToNdc pos (State state)
    in
    if state.dragging then
        if alter then
            panMouse ndcPos (State state)

        else
            rotateMouse ndcPos (State state)

    else
        State { state | ndcPos = ndcPos }


updateZoom : Float -> Bool -> State -> State
updateZoom value alter (State state) =
    let
        factor =
            if value > 0 then
                0.9

            else if value < 0 then
                1 / 0.9

            else
                1.0

        newState =
            if alter then
                { state
                    | fieldOfView =
                        clamp 2.5 150 <| factor * state.fieldOfView
                }

            else
                { state
                    | cameraDistance =
                        clamp 2.5 1000 <| factor * state.cameraDistance
                }
    in
    State newState


setFrameSize : { width : Int, height : Int } -> State -> State
setFrameSize size (State state) =
    State
        { state
            | size =
                { width = toFloat size.width
                , height = toFloat size.height
                }
        }


startDragging : { x : Int, y : Int } -> State -> State
startDragging pos (State state) =
    State
        { state
            | dragging = True
            , moving = True
            , ndcPos = positionToNdc pos (State state)
            , deltaRot = Mat4.identity
        }


finishDragging : State -> State
finishDragging (State state) =
    State
        { state
            | dragging = False
            , moving = state.milliSecsSinceMoved < 100
        }


lookAlong : Vec3 -> Vec3 -> State -> State
lookAlong axis up (State state) =
    State { state | rotation = Mat4.makeLookAt (vec3 0 0 0) axis up }


encompass : Vec3 -> Float -> State -> State
encompass center radius (State state) =
    let
        dist =
            radius / sin (degrees state.fieldOfView / 2)
    in
    State
        { state
            | shift = Vec3.scale -1 center
            , cameraDistance = clamp 2.5 1000 dist
        }


panMouse : Position -> State -> State
panMouse ndcPosNew (State state) =
    let
        dx =
            ndcPosNew.x - state.ndcPos.x

        dy =
            ndcPosNew.y - state.ndcPos.y

        invRot =
            Mat4.inverseOrthonormal state.rotation

        shift =
            Mat4.transform invRot <| vec3 dx dy 0
    in
    State
        { state
            | ndcPos = ndcPosNew
            , shift = Vec3.add state.shift shift
        }


rotateMouse : Position -> State -> State
rotateMouse ndcPosNew (State state) =
    let
        ( axis, angle ) =
            rotationParameters ndcPosNew state.ndcPos

        deltaRot =
            Mat4.makeRotate angle axis

        rotation =
            orthonormalized <| Mat4.mul deltaRot state.rotation
    in
    State
        { state
            | ndcPos = ndcPosNew
            , deltaRot = deltaRot
            , rotation = rotation
            , milliSecsSinceMoved = 0
        }


zRotationAngle : Float -> Float -> Float -> Float -> Float
zRotationAngle px py dx dy =
    if px > 0.9 then
        dy

    else if px < -0.9 then
        -dy

    else if py > 0.9 then
        -dx

    else if py < -0.9 then
        dx<commit_msg>GUI: ensure constant rotation speed when spinning<commit_after>module Camera exposing
    ( State
    , cameraDistance
    , encompass
    , finishDragging
    , initialState
    , isDragging
    , isMoving
    , lookAlong
    , nextFrame
    , perspectiveMatrix
    , setFrameSize
    , setMousePosition
    , setRedraws
    , startDragging
    , updateZoom
    , viewingMatrix
    )

import Math.Matrix4 as Mat4 exposing (Mat4)
import Math.Vector3 as Vec3 exposing (Vec3, vec3)
import Time exposing (Posix)


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


type State
    = State
        { size : { width : Float, height : Float }
        , origin : Position
        , cameraDistance : Float
        , fieldOfView : Float
        , dragging : Bool
        , moving : Bool
        , ndcPos : Position
        , shift : Vec3
        , rotation : Mat4
        , spinAxis : Vec3
        , spinAngle : Float
        , milliSecsSinceMoved : Float
        }


initialState : State
initialState =
    State
        { size = { width = 0, height = 0 }
        , origin = { x = 0, y = 0 }
        , cameraDistance = 12
        , fieldOfView = 25
        , dragging = False
        , moving = False
        , ndcPos = { x = 0, y = 0 }
        , rotation = Mat4.identity
        , spinAxis = vec3 0 0 1
        , spinAngle = 0
        , milliSecsSinceMoved = 0
        , shift = vec3 0 0 0
        }


nextFrame : Float -> State -> State
nextFrame timeInMilliSecs (State state) =
    if state.dragging then
        State
            { state
                | milliSecsSinceMoved =
                    state.milliSecsSinceMoved + timeInMilliSecs
            }

    else if state.moving then
        let
            angle =
                state.spinAngle * timeInMilliSecs

            deltaRot =
                Mat4.makeRotate angle state.spinAxis

            rotation =
                orthonormalized <| Mat4.mul deltaRot state.rotation
        in
        State { state | rotation = rotation }

    else
        State state


positionToNdc : { x : Int, y : Int } -> State -> Position
positionToNdc pos (State state) =
    let
        xRelative =
            (toFloat pos.x - state.origin.x) / state.size.width

        yRelative =
            (toFloat pos.y - state.origin.y) / state.size.height
    in
    { x = 2 * xRelative - 1, y = 1 - 2 * yRelative }


setMousePosition : { x : Int, y : Int } -> Bool -> State -> State
setMousePosition pos alter (State state) =
    let
        ndcPos =
            positionToNdc pos (State state)
    in
    if state.dragging then
        if alter then
            panMouse ndcPos (State state)

        else
            rotateMouse ndcPos (State state)

    else
        State { state | ndcPos = ndcPos }


updateZoom : Float -> Bool -> State -> State
updateZoom value alter (State state) =
    let
        factor =
            if value > 0 then
                0.9

            else if value < 0 then
                1 / 0.9

            else
                1.0

        newState =
            if alter then
                { state
                    | fieldOfView =
                        clamp 2.5 150 <| factor * state.fieldOfView
                }

            else
                { state
                    | cameraDistance =
                        clamp 2.5 1000 <| factor * state.cameraDistance
                }
    in
    State newState


setFrameSize : { width : Int, height : Int } -> State -> State
setFrameSize size (State state) =
    State
        { state
            | size =
                { width = toFloat size.width
                , height = toFloat size.height
                }
        }


startDragging : { x : Int, y : Int } -> State -> State
startDragging pos (State state) =
    State
        { state
            | dragging = True
            , moving = True
            , ndcPos = positionToNdc pos (State state)
            , spinAngle = 0
        }


finishDragging : State -> State
finishDragging (State state) =
    State
        { state
            | dragging = False
            , moving = state.milliSecsSinceMoved < 100
        }


lookAlong : Vec3 -> Vec3 -> State -> State
lookAlong axis up (State state) =
    State { state | rotation = Mat4.makeLookAt (vec3 0 0 0) axis up }


encompass : Vec3 -> Float -> State -> State
encompass center radius (State state) =
    let
        dist =
            radius / sin (degrees state.fieldOfView / 2)
    in
    State
        { state
            | shift = Vec3.scale -1 center
            , cameraDistance = clamp 2.5 1000 dist
        }


panMouse : Position -> State -> State
panMouse ndcPosNew (State state) =
    let
        dx =
            ndcPosNew.x - state.ndcPos.x

        dy =
            ndcPosNew.y - state.ndcPos.y

        invRot =
            Mat4.inverseOrthonormal state.rotation

        shift =
            Mat4.transform invRot <| vec3 dx dy 0
    in
    State
        { state
            | ndcPos = ndcPosNew
            , shift = Vec3.add state.shift shift
        }


rotateMouse : Position -> State -> State
rotateMouse ndcPosNew (State state) =
    let
        ( axis, angle ) =
            rotationParameters ndcPosNew state.ndcPos

        deltaRot =
            Mat4.makeRotate angle axis

        rotation =
            orthonormalized <| Mat4.mul deltaRot state.rotation
    in
    State
        { state
            | ndcPos = ndcPosNew
            , spinAxis = axis
            , spinAngle = 60 * angle / 1000
            , rotation = rotation
            , milliSecsSinceMoved = 0
        }


zRotationAngle : Float -> Float -> Float -> Float -> Float
zRotationAngle px py dx dy =
    if px > 0.9 then
        dy

    else if px < -0.9 then
        -dy

    else if py > 0.9 then
        -dx

    else if py < -0.9 then
        dx
{'fraction_non_alphanumeric': 0.0765297125727385, 'fraction_numerical': 0.016663727737612415, 'mean_word_length': 1.380482686253935, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5898886', 'n_tokens_mistral': 3408, 'n_tokens_neox': 3172, 'n_words': 963}