Skip to content

Instantly share code, notes, and snippets.

@helgee
Created February 5, 2020 13:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save helgee/9052a3b6507da88d6879df1790f5509c to your computer and use it in GitHub Desktop.
Save helgee/9052a3b6507da88d6879df1790f5509c to your computer and use it in GitHub Desktop.
elm-3d-scene Earth Test
module OrbitingEarth exposing (main)
import Angle exposing (Angle)
import Axis3d exposing (Axis3d)
import Browser
import Browser.Events
import Camera3d exposing (Camera3d)
import Color exposing (Color)
import Direction3d
import Frame3d exposing (Frame3d)
import Html exposing (Html)
import Illuminance
import Json.Decode as Decode exposing (Decoder)
import Length exposing (Meters)
import Luminance
import LuminousFlux
import Palette.Tango as Tango
import Parameter1d
import Pixels
import Point3d
import Quantity
import Scene3d
import Scene3d.Chromaticity as Chromaticity
import Scene3d.Exposure as Exposure
import Scene3d.Material as Material exposing (Material)
import Sphere3d
import Task
import Temperature
import Vector3d exposing (Vector3d)
import Viewpoint3d exposing (Viewpoint3d)
import WebGL.Texture
type SphereCoordinates
= SphereCoordinates
type WorldCoordinates
= WorldCoordinates
type Model
= Loading
{ colorTexture : Maybe (Material.Texture Color)
, roughnessTexture : Maybe (Material.Texture Float)
, normalMapTexture : Maybe (Material.Texture Material.NormalMap)
}
| Loaded
{ colorTexture : Material.Texture Color
, roughnessTexture : Material.Texture Float
, normalMapTexture : Material.Texture Material.NormalMap
, sphereFrame : Frame3d Meters WorldCoordinates { defines : SphereCoordinates }
, orbiting : Bool
}
| Errored String
type Msg
= GotColorTexture (Result WebGL.Texture.Error (Material.Texture Color))
| GotRoughnessTexture (Result WebGL.Texture.Error (Material.Texture Float))
| GotNormalMapTexture (Result WebGL.Texture.Error (Material.Texture Material.NormalMap))
| MouseDown
| MouseUp
| MouseMove Float Float
init : ( Model, Cmd Msg )
init =
( Loading
{ colorTexture = Nothing
, roughnessTexture = Nothing
, normalMapTexture = Nothing
}
, Cmd.batch
[ Material.load "2k_earth_daymap.jpg"
|> Task.attempt GotColorTexture
, Material.load "2k_earth_specular_map_neg.jpg"
|> Task.attempt GotRoughnessTexture
, Material.load "2k_earth_normal_map.jpg"
|> Task.attempt GotNormalMapTexture
]
)
update : Msg -> Model -> ( Model, Cmd Msg )
update message model =
let
updatedModel =
case model of
Loading textures ->
case message of
GotColorTexture (Ok colorTexture) ->
checkIfLoaded { textures | colorTexture = Just colorTexture }
GotRoughnessTexture (Ok roughnessTexture) ->
checkIfLoaded { textures | roughnessTexture = Just roughnessTexture }
GotNormalMapTexture (Ok normalMapTexture) ->
checkIfLoaded { textures | normalMapTexture = Just normalMapTexture }
GotColorTexture (Err error) ->
Errored "Error loading color texture"
GotRoughnessTexture (Err error) ->
Errored "Error loading roughness texture"
GotNormalMapTexture (Err error) ->
Errored "Error loading normal map texture"
MouseDown ->
model
MouseUp ->
model
MouseMove _ _ ->
model
Loaded loadedModel ->
case message of
GotColorTexture _ ->
model
GotRoughnessTexture _ ->
model
GotNormalMapTexture _ ->
model
MouseDown ->
Loaded { loadedModel | orbiting = True }
MouseUp ->
Loaded { loadedModel | orbiting = False }
MouseMove dx dy ->
if loadedModel.orbiting then
let
rotationVector =
Vector3d.withLength (Angle.degrees dx)
(Viewpoint3d.yDirection viewpoint)
|> Vector3d.plus
(Vector3d.withLength (Angle.degrees dy)
(Viewpoint3d.xDirection viewpoint)
)
in
case Vector3d.direction rotationVector of
Just direction ->
let
newFrame =
loadedModel.sphereFrame
|> Frame3d.rotateAround
(Axis3d.through (Frame3d.originPoint loadedModel.sphereFrame) direction)
(Vector3d.length rotationVector)
in
Loaded { loadedModel | sphereFrame = newFrame }
Nothing ->
model
else
model
Errored _ ->
model
in
( updatedModel, Cmd.none )
checkIfLoaded :
{ colorTexture : Maybe (Material.Texture Color)
, roughnessTexture : Maybe (Material.Texture Float)
, normalMapTexture : Maybe (Material.Texture Material.NormalMap)
}
-> Model
checkIfLoaded textures =
case ( textures.colorTexture, textures.roughnessTexture, textures.normalMapTexture ) of
( Just colorTexture, Just roughnessTexture, Just normalMapTexture ) ->
Loaded
{ colorTexture = colorTexture
, roughnessTexture = roughnessTexture
, normalMapTexture = normalMapTexture
, sphereFrame = Frame3d.atOrigin
, orbiting = False
}
_ ->
Loading textures
viewpoint : Viewpoint3d Meters WorldCoordinates
viewpoint =
Viewpoint3d.lookAt
{ focalPoint = Point3d.origin
, eyePoint = Point3d.centimeters 20 10 10
, upDirection = Direction3d.positiveZ
}
sunlight =
Scene3d.directionalLight Scene3d.doesNotCastShadows
{ chromaticity = Chromaticity.sunlight
, intensity = Illuminance.lux 20000
, direction = Direction3d.yz (Angle.degrees -120)
}
camera : Camera3d Meters WorldCoordinates
camera =
Camera3d.perspective
{ viewpoint = viewpoint
, verticalFieldOfView = Angle.degrees 30
, clipDepth = Length.centimeters 0.5
}
view : Model -> Html msg
view model =
case model of
Loaded { colorTexture, roughnessTexture, normalMapTexture, sphereFrame } ->
let
material =
Material.normalMappedPbr
{ baseColor = Material.constant Tango.skyBlue2
, roughness = roughnessTexture
, metallic = Material.constant 0
, normalMap = normalMapTexture
}
in
Scene3d.toHtml
{ camera = camera
, width = Pixels.pixels 800
, height = Pixels.pixels 600
, environmentalLighting =
Scene3d.softLighting
{ upDirection = Direction3d.positiveZ
, above = ( Luminance.nits 3000, Chromaticity.d65 )
, below = ( Quantity.zero, Chromaticity.d65 )
}
, directLighting =
Scene3d.oneLightSource sunlight
, exposure = Exposure.fromEv100 12
, whiteBalance = Scene3d.defaultWhiteBalance
, backgroundColor = Scene3d.transparentBackground
}
[ Sphere3d.withRadius (Length.centimeters 5) Point3d.origin
|> Scene3d.sphere Scene3d.doesNotCastShadows material
|> Scene3d.placeIn sphereFrame
]
Loading _ ->
Html.text "Loading..."
Errored message ->
Html.text message
decodeMouseMove : Decoder Msg
decodeMouseMove =
Decode.map2 MouseMove
(Decode.field "movementX" Decode.float)
(Decode.field "movementY" Decode.float)
subscriptions : Model -> Sub Msg
subscriptions model =
case model of
Loading _ ->
Sub.none
Errored _ ->
Sub.none
Loaded { orbiting } ->
if orbiting then
Sub.batch
[ Browser.Events.onMouseMove decodeMouseMove
, Browser.Events.onMouseUp (Decode.succeed MouseUp)
]
else
Browser.Events.onMouseDown (Decode.succeed MouseDown)
main : Program () Model Msg
main =
Browser.element
{ init = always init
, update = update
, view = view
, subscriptions = subscriptions
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment