381 lines
13 KiB
Elm
381 lines
13 KiB
Elm
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
|
|
|