module Scene exposing (..) import Html exposing (Html) import Html.Attributes exposing (width, height, style) import AnimationFrame import Time exposing (Time) import Dict exposing (..) import Math.Matrix4 as Mat4 exposing (Mat4) import Math.Vector2 as Vec2 exposing (vec2, Vec2) import Math.Vector3 as Vec3 exposing (vec3, Vec3) import WebGL exposing (Mesh, Shader) import Mouse import Keyboard exposing (..) import Task import WebGL.Texture as Texture exposing (..) import Color exposing (..) import Window import OBJ import OBJ.Types import Shaders exposing (..) type alias ObjVert = OBJ.Types.Vertex type alias ObjMesh = OBJ.Types.MeshWith ObjVert main : Program Never Model Msg main = Html.program { init = init , view = view , update = update , subscriptions = subscriptions } type alias Model = { texDict : Dict String Texture , objDict : Dict String ObjMesh , rot : Float , winSize : Window.Size , keys : Keys , pos : Vec3 , pitchAndYaw : ( Float , Float ) , lookDir : Vec3 , lastMousePos : Mouse.Position , robot : Robot } type alias Robot = { pos : Vec3 , rot : Float , armRot : Float , handRot : Float } type alias Keys = { left : Bool , down : Bool , up : Bool , right : Bool , w : Bool , a : Bool , s : Bool , d : Bool , y : Bool , h : Bool , u : Bool , j : Bool , n : Bool , m : Bool } type Msg = TextureLoaded (Result Texture.Error (String , Texture)) | ObjLoaded (Result String (String , ObjMesh)) | Animate Time | WindowResized Window.Size | MouseMove Mouse.Position | KeyChange Bool Keyboard.KeyCode init: ( Model , Cmd Msg ) init = ( Model Dict.empty Dict.empty 0 (Window.Size 1 1) -- God this is horrendous (Keys False False False False False False False False False False False False False False) (vec3 0 0 -10) (0 , -90) (vec3 0 0 1) { x = 0 , y = 0 } { pos = vec3 0 -0.5 -3 , rot = 45 , armRot = 0 , handRot = 0 } , Cmd.batch [ loadTex "Thwomp" "textures/thwomp-face.jpg" , loadTex "UV" "textures/uv_big.png" , loadTex "Tetra" "textures/tetra.png" , loadObj "Teapot" "models/suz.obj" , Task.perform WindowResized Window.size] ) loadTex: String -> String -> Cmd Msg loadTex id path = Task.attempt TextureLoaded (Texture.load path |> Task.map (\t -> (id , t) ) ) loadObj: String -> String -> Cmd Msg loadObj id path = OBJ.loadMeshWithoutTexture path (\ r -> Result.map (\ o -> (id , o)) r ) |> Cmd.map ObjLoaded subscriptions: Model -> Sub Msg subscriptions _ = Sub.batch [ AnimationFrame.diffs Animate , Window.resizes WindowResized , Mouse.moves MouseMove , Keyboard.downs (KeyChange True) , Keyboard.ups (KeyChange False) ] update: Msg -> Model -> (Model , Cmd Msg) update msg model = let m = case msg of KeyChange b k -> { model | keys = getKeys b k model.keys } Animate dt -> { model | pos = movePos (model.keys.a , model.keys.s , model.keys.w , model.keys.d) model.lookDir model.pos 0.11 , robot = updateRobot model dt , rot = model.rot + 0.001 * dt} WindowResized size -> { model | winSize = size } TextureLoaded result -> case result of Ok (id , tex) -> { model | texDict = Dict.insert id tex model.texDict } Err e -> model ObjLoaded result -> case result of Ok (id , obj) -> { model | objDict = Dict.insert id obj model.objDict } Err e -> model |> Debug.log e MouseMove mp -> let (ld , py) = getLookPos model.lastMousePos mp model.pitchAndYaw in { model | lookDir = ld , lastMousePos = mp , pitchAndYaw = py } in ( m , Cmd.none ) updateRobot: Model -> Time -> Robot updateRobot { robot , keys } dt = let rotSpeed = 0.003 rr part = part + dt * rotSpeed rl part = part - dt * rotSpeed rot = radians robot.rot in { pos = movePos (keys.left, keys.down, keys.up, keys.right) (vec3 (sin rot) (sin rot) (cos rot)) robot.pos 0.05 , rot = if keys.n then rr robot.rot else if keys.m then rl robot.rot else robot.rot , armRot = let angle = if keys.j then rr robot.armRot else if keys.h then rl robot.armRot else robot.armRot in clamp -0.5 2.5 angle , handRot = let angle = if keys.u then rr robot.handRot else if keys.y then rl robot.handRot else robot.handRot in clamp -1 1 angle} view: Model -> Html Msg view model = WebGL.toHtml [ width model.winSize.width , height model.winSize.height , style [ ( "display" , "block") ] ] ([ getEntity model wall texturedPlane "Thwomp" , getEntity model tetraB tetraBasic "UV" , getEntity model tetra tetraF "Tetra" , getRobot model , getModel model (Mat4.makeRotate model.rot Vec3.j) "Teapot" , getEntity model floor texturedPlane "UV" ] |> List.concat) getRobot: Model -> List WebGL.Entity getRobot model = let body = Mat4.makeTranslate model.robot.pos |> Mat4.rotate model.robot.rot Vec3.j arm = Mat4.makeTranslate3 0 0 -0.35 |> Mat4.rotate model.robot.armRot Vec3.i |> Mat4.inverse |> Maybe.withDefault Mat4.identity |> Mat4.mul (Mat4.makeTranslate3 0 0.5 0.5) |> Mat4.mul body hand = Mat4.makeTranslate3 0 0 -0.25 |> Mat4.rotate model.robot.handRot Vec3.i |> Mat4.inverse |> Maybe.withDefault Mat4.identity |> Mat4.mul (Mat4.makeTranslate3 0 0 0.6) |> Mat4.mul arm in [ getEntity3 model (body |> Mat4.scale3 0.5 0.5 0.5) cube Color.blue , getEntity3 model (arm |> Mat4.scale3 0.2 0.2 0.5) cube Color.green , getEntity3 model (hand |> Mat4.scale3 0.15 0.15 0.45) cube Color.red ] |> List.concat getModel: Model -> Mat4 -> String -> List WebGL.Entity getModel model local id = case Dict.get id model.objDict of Just mesh -> case Dict.get "UV" model.texDict of Just t -> [ WebGL.entity diffuseVS diffuseFS (WebGL.indexedTriangles mesh.vertices mesh.indices) (DiffuseColor (projectionMatrix model) (viewMatrix model) local (colorToVec3 Color.blue) (vec3 1 1 0) (vec3 1 0 1) (vec3 0 0 1) 1.0) ] Nothing -> [] Nothing -> [] getEntity: Model -> Mat4 -> Mesh UTVertex -> String -> List WebGL.Entity getEntity model local mesh texId = case Dict.get texId model.texDict of Just t -> [ WebGL.entity unlitTexturedVS unlitTexturedFS mesh (UnlitTextured (projectionMatrix model) (viewMatrix model ) local t) ] Nothing -> [] getEntity2: Model -> Mat4 -> Mesh ObjVert -> Color -> List WebGL.Entity getEntity2 model local mesh color = [ WebGL.entity unlitColorVS unlitColorFS mesh (UnlitColor (projectionMatrix model) (viewMatrix model ) local (colorToVec3 color) ) ] getEntity3: Model -> Mat4 -> Mesh ObjVert -> Color -> List WebGL.Entity getEntity3 model local mesh color = [ WebGL.entity diffuseVS diffuseFS mesh (DiffuseColor (projectionMatrix model) (viewMatrix model) local (colorToVec3 color) (vec3 1 1 0) (vec3 1 0 1) (vec3 0 0 1) 1.0 ) ] projectionMatrix: Model -> Mat4 projectionMatrix model = Mat4.makePerspective 50 (toFloat model.winSize.width / toFloat model.winSize.height) 0.01 1000 viewMatrix: Model -> Mat4 viewMatrix model = Mat4.makeLookAt model.pos (Vec3.add model.pos model.lookDir) Vec3.j getLookPos: Mouse.Position -> Mouse.Position -> ( Float , Float ) -> ( Vec3 , (Float , Float) ) getLookPos lmp mp ( lastPitch , lastYaw ) = let sensitivity = 0.0039 rangeY = 89 ox = mp.x - lmp.x |> toFloat oy = lmp.y - mp.y |> toFloat yaw = ox * sensitivity + lastYaw |> radians pitch = -oy * sensitivity + lastPitch |> radians pitch_ = if pitch > rangeY then rangeY else if pitch < -rangeY then -rangeY else pitch lookDir = vec3 (cos yaw * cos pitch_) (sin pitch_) (sin yaw * cos pitch_) in (Vec3.normalize lookDir , ( pitch_ , yaw ) ) colorToVec3: Color -> Vec3 colorToVec3 color = let to01 x = toFloat x / 255 c = Color.toRgb color in vec3 (to01 c.red) (to01 c.green) (to01 c.blue) movePos: (Bool, Bool, Bool, Bool) -> Vec3 -> Vec3 -> Float -> Vec3 movePos ( left , down , up , right ) lookDir pos speed = let lookDir_ = Vec3.setY 0 lookDir forward = if up then 1 else if down then -1 else 0 strafe = if right then 1 else if left then -1 else 0 cross = Vec3.cross lookDir_ Vec3.j dir = Vec3.add (Vec3.scale strafe cross) (Vec3.scale forward lookDir_) dir_ = if Vec3.length dir <= 0 then dir else Vec3.normalize dir in Vec3.add pos <| Vec3.scale speed dir_ getKeys: Bool -> KeyCode -> Keys -> Keys getKeys isOn code keys = case code of -- ◀ ▼ ▲ ▶ 37 -> { keys | left = isOn } 40 -> { keys | down = isOn } 38 -> { keys | up = isOn } 39 -> { keys | right = isOn } -- WASD 87 -> { keys | w = isOn } 65 -> { keys | a = isOn } 83 -> { keys | s = isOn } 68 -> { keys | d = isOn } -- YHUJNM 89 -> { keys | y = isOn } 72 -> { keys | h = isOn } 85 -> { keys | u = isOn } 74 -> { keys | j = isOn } 78 -> { keys | n = isOn } 77 -> { keys | m = isOn } _ -> keys ------------- -- Geometry ------------- wall = Mat4.makeTranslate3 0 0 3 floor = Mat4.makeTranslate3 0 -1 0 |> Mat4.rotate (pi / -2) ( vec3 1 0 0) |> Mat4.rotate pi ( vec3 0 0 1) |> Mat4.scale3 15 15 0 tetraB = Mat4.makeTranslate3 -5 1.5 5 |> Mat4.scale3 2 2 2 tetra = Mat4.makeTranslate3 5 0 5 -- right/left front/back top/bottom cube: Mesh ObjVert cube = let rtf = vec3 1 1 1 ltf = vec3 -1 1 1 ltb = vec3 -1 1 -1 rtb = vec3 1 1 -1 rbb = vec3 1 -1 -1 rbf = vec3 1 -1 1 lbf = vec3 -1 -1 1 lbb = vec3 -1 -1 -1 front = Vec3.k back = Vec3.scale -1 front top = Vec3.j bottom = Vec3.scale -1 top right = Vec3.i left = Vec3.scale -1 right in [ face right rtf rbf rbb rtb , face left ltf lbf lbb ltb , face front rtf rbf lbf ltf , face back rtb rbb lbb ltb , face top rtf ltf ltb rtb , face bottom rbf lbf lbb rbb ] |> List.concat |> WebGL.triangles face: Vec3 -> Vec3 -> Vec3 -> Vec3 -> Vec3 -> List (ObjVert , ObjVert , ObjVert) face norm a b c d = let v pos = OBJ.Types.Vertex pos norm in [ ( v a , v b , v c ) , ( v c , v d , v a ) ] tetraBasic: Mesh UTVertex tetraBasic = let peak = UTVertex (vec3 0 1 0) (vec2 1 1) bottomLeft = UTVertex (vec3 -1 -1 -1) (vec2 0 0) bottomRight = UTVertex (vec3 -1 -1 1) (vec2 1 0) topLeft = UTVertex (vec3 1 -1 1) (vec2 0 0) topRight = UTVertex (vec3 1 -1 -1) (vec2 0 1) in [ ( peak , bottomLeft , bottomRight ) , ( peak , bottomLeft , topRight ) , ( peak , bottomRight , topLeft ) , ( peak , topRight , topLeft ) , ( bottomLeft , bottomRight , topRight) , ( bottomRight, topLeft , topRight ) ] |> WebGL.triangles tetraF: Mesh UTVertex tetraF = let f0a = UTVertex (vec3 -1 -1 1) (vec2 0 0.5) f0b = UTVertex (vec3 1 -1 1) (vec2 0.5 0.5) f0c = UTVertex (vec3 0 1 0) (vec2 0.25 1) f1a = UTVertex (vec3 -1 -1 -1) (vec2 0.5 0.5) f1b = UTVertex (vec3 -1 -1 1) (vec2 1 0.5) f1c = UTVertex (vec3 0 1 0) (vec2 0.75 1) f2a = UTVertex (vec3 1 -1 -1) (vec2 0 0) f2b = UTVertex (vec3 -1 -1 -1) (vec2 0.5 0) f2c = UTVertex (vec3 0 1 0) (vec2 0.25 0.5) f3a = UTVertex (vec3 1 -1 1) (vec2 0.5 0) f3b = UTVertex (vec3 1 -1 -1) (vec2 1 0) f3c = UTVertex (vec3 0 1 0) (vec2 0.75 0.5) in [ ( f0a , f0b , f0c ) , ( f1a , f1b , f1c ) , ( f2a , f2b , f2c ) , ( f3a , f3b , f3c ) ] |> WebGL.triangles texturedPlane: Mesh UTVertex texturedPlane = let topLeft = UTVertex (vec3 -1 1 1) (vec2 0 1) topRight = UTVertex (vec3 1 1 1) (vec2 1 1) bottomLeft = UTVertex (vec3 -1 -1 1) (vec2 0 0) bottomRight = UTVertex (vec3 1 -1 1) (vec2 1 0) in [ ( topLeft, topRight, bottomLeft ) , ( bottomLeft, topRight, bottomRight ) ] |> WebGL.triangles