Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skybox feature #104

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ elm.js
.AppleDouble
.LSOverride

# VSCode specific files
.vscode

# Thumbnails
._*

Expand Down
201 changes: 201 additions & 0 deletions examples/Skybox.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
module Skybox exposing (main)

{-| This example shows how to load and set a skybox texture. The exmple also
implements camera rotation, and the skybox can be previewed from different
angles.
-}

import Angle exposing (Angle)
import Block3d
import Browser
import Browser.Dom
import Browser.Events
import Camera3d
import Color
import Direction3d
import Frame3d
import Json.Decode as Decode exposing (Decoder)
import Length
import Pixels exposing (Pixels)
import Point3d exposing (Point3d)
import Quantity exposing (Quantity)
import Scene3d
import Scene3d.Material as Material
import Scene3d.Skybox as Skybox exposing (Skybox, loadEquirectangular)
import Task
import Viewpoint3d
import WebGL.Texture


type alias Model =
{ width : Quantity Int Pixels -- Width of the browser window
, height : Quantity Int Pixels -- Height of the browser window
, orbiting : Bool
, azimuth : Angle
, elevation : Angle
, skybox : Maybe Skybox
}


type Msg
= MouseUp
| MouseDown
| Resize (Quantity Int Pixels) (Quantity Int Pixels)
| MouseMove (Quantity Float Pixels) (Quantity Float Pixels)
| SkyboxLoaded (Result WebGL.Texture.Error Skybox)


type WorldCoordinates
= WorldCoordinates


main : Program () Model Msg
main =
Browser.document
{ init = init
, update = update
, view = view
, subscriptions = subs
}


init : () -> ( Model, Cmd Msg )
init _ =
( { width = Quantity.zero
, height = Quantity.zero
, orbiting = False
, azimuth = Angle.degrees 135
, elevation = Angle.degrees 5
, skybox = Nothing
}
, Cmd.batch
[ Task.perform
(\{ viewport } ->
Resize
(Pixels.int (round viewport.width))
(Pixels.int (round viewport.height))
)
Browser.Dom.getViewport

-- Load equirectangular texture
, "https://ianmackenzie.github.io/elm-3d-scene/examples/skybox/umhlanga_sunrise_8k.jpg"
|> Skybox.loadEquirectangular
|> Task.attempt SkyboxLoaded
]
)


mouseMoveDecoder : Decoder Msg
mouseMoveDecoder =
Decode.map2 MouseMove
(Decode.field "movementX" (Decode.map Pixels.float Decode.float))
(Decode.field "movementY" (Decode.map Pixels.float Decode.float))


subs : Model -> Sub Msg
subs model =
Sub.batch
[ Browser.Events.onResize
(\w h -> Resize (Pixels.int w) (Pixels.int h))

--
, if model.orbiting then
Sub.batch
[ Browser.Events.onMouseMove mouseMoveDecoder
, Browser.Events.onMouseUp (Decode.succeed MouseUp)
]

else
Browser.Events.onMouseDown (Decode.succeed MouseDown)
]


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Resize w h ->
( { model | width = w, height = h }, Cmd.none )

MouseDown ->
( { model | orbiting = True }, Cmd.none )

MouseUp ->
( { model | orbiting = False }, Cmd.none )

MouseMove dx dy ->
if model.orbiting then
let
rotationRate =
Angle.degrees -0.05 |> Quantity.per Pixels.pixel

newAzimuth =
model.azimuth
|> Quantity.minus (dx |> Quantity.at rotationRate)

newElevation =
model.elevation
|> Quantity.plus (dy |> Quantity.at rotationRate)
|> Quantity.clamp (Angle.degrees -60) (Angle.degrees 60)
in
( { model
| orbiting = True
, azimuth = newAzimuth
, elevation = newElevation
}
, Cmd.none
)

else
( model, Cmd.none )

SkyboxLoaded (Ok texture) ->
( { model | skybox = Just texture }, Cmd.none )

SkyboxLoaded (Err err) ->
( model, Cmd.none )


view : Model -> Browser.Document Msg
view model =
let
camera =
Camera3d.perspective
{ viewpoint =
Viewpoint3d.orbitZ
{ focalPoint = Point3d.centimeters 0 0 20
, azimuth = model.azimuth
, elevation = model.elevation
, distance = Length.meters 3
}
, verticalFieldOfView = Angle.degrees 30
}
in
{ title = "Skybox"
, body =
[ Scene3d.sunny
{ upDirection = Direction3d.z
, sunlightDirection = Direction3d.xyZ (Angle.degrees -135) (Angle.degrees -45)
, shadows = False
, dimensions = ( model.width, model.height )
, camera = camera
, clipDepth = Length.centimeters 10
, background =
model.skybox
|> Maybe.map Scene3d.backgroundSkybox
|> Maybe.withDefault (Scene3d.backgroundColor Color.lightBlue)
, entities =
[ Scene3d.block
(Material.matte Color.lightBrown)
(Block3d.centeredOn
(Frame3d.atPoint
(Point3d.centimeters 0 0 20)
)
( Length.centimeters 0
, Length.centimeters 0
, Length.centimeters 0
)
)
]
}
]
}
81 changes: 65 additions & 16 deletions src/Scene3d.elm
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Scene3d exposing
, mesh, meshWithShadow
, group, nothing
, rotateAround, translateBy, translateIn, scaleAbout, mirrorAcross
, Background, transparentBackground, backgroundColor
, Background, transparentBackground, backgroundColor, backgroundSkybox
, Antialiasing
, noAntialiasing, multisampling, supersampling
, Lights
Expand Down Expand Up @@ -114,7 +114,7 @@ entity:

# Background

@docs Background, transparentBackground, backgroundColor
@docs Background, transparentBackground, backgroundColor, backgroundSkybox


# Antialiasing
Expand Down Expand Up @@ -213,6 +213,8 @@ import Scene3d.Entity as Entity
import Scene3d.Light as Light exposing (Chromaticity, Light)
import Scene3d.Material as Material exposing (Material)
import Scene3d.Mesh as Mesh exposing (Mesh)
import Scene3d.Skybox
import Scene3d.Skybox.Protected
import Scene3d.Transformation as Transformation exposing (Transformation)
import Scene3d.Types as Types exposing (Bounds, DrawFunction, LightMatrices, LinearRgb(..), Material(..), Node(..))
import Sphere3d exposing (Sphere3d)
Expand Down Expand Up @@ -937,6 +939,7 @@ current environmental lighting.
-}
type Background coordinates
= BackgroundColor Color
| BackgroundSkybox Scene3d.Skybox.Skybox


{-| A fully transparent background.
Expand All @@ -953,6 +956,36 @@ backgroundColor color =
BackgroundColor color


toBackgroundColorString : Background coordinates -> Maybe String
toBackgroundColorString bkg =
case bkg of
BackgroundColor color ->
Just (Color.toCssString color)

_ ->
Nothing


{-| Provides a way to set a skybox background!

Before the skybox can be set, a skybox texture must be loaded first. At the
moment, only equirectangular skybox textures are supported. On how to load a
skybox texture, please refer to the `Scene3d.Skybox` module.

Once the skybox texture is ready, it can be set as the background when
initialising a `Scene3d` scene. For example:

Scene3d.sunny
{ background = Scene3d.backgroundSkybox loadedSkyboxTexture
, ...
}

-}
backgroundSkybox : Scene3d.Skybox.Skybox -> Background coordinates
backgroundSkybox texture =
BackgroundSkybox texture



----- RENDERING -----

Expand Down Expand Up @@ -1698,12 +1731,6 @@ composite arguments scenes =
heightInPixels =
Pixels.toInt height

(BackgroundColor givenBackgroundColor) =
arguments.background

backgroundColorString =
Color.toCssString givenBackgroundColor

commonWebGLOptions =
[ WebGL.depth 1
, WebGL.stencil 0
Expand Down Expand Up @@ -1753,14 +1780,36 @@ composite arguments scenes =
Html.Keyed.node "div" [ Html.Attributes.style "padding" "0px", widthCss, heightCss ] <|
[ ( key
, WebGL.toHtmlWith webGLOptions
[ Html.Attributes.width (round (toFloat widthInPixels * scalingFactor))
, Html.Attributes.height (round (toFloat heightInPixels * scalingFactor))
, widthCss
, heightCss
, Html.Attributes.style "display" "block"
, Html.Attributes.style "background-color" backgroundColorString
]
webGLEntities
([ Html.Attributes.width (round (toFloat widthInPixels * scalingFactor))
, Html.Attributes.height (round (toFloat heightInPixels * scalingFactor))
, widthCss
, heightCss
, Html.Attributes.style "display" "block"
]
++ (case toBackgroundColorString arguments.background of
Just colorStr ->
[ Html.Attributes.style "background-color" colorStr
]

Nothing ->
[]
)
)
(List.concat
[ case arguments.background of
BackgroundSkybox skybox ->
[ Scene3d.Skybox.Protected.quad
{ camera = arguments.camera
, aspectRatio = aspectRatio
, skybox = skybox
}
]

_ ->
[]
, webGLEntities
]
)
)
]

Expand Down
47 changes: 47 additions & 0 deletions src/Scene3d/Skybox.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module Scene3d.Skybox exposing
( Skybox
, loadEquirectangular
)

{-|

@docs Skybox


# Loading a Skybox texture

@docs loadEquirectangular

-}

import Scene3d.Skybox.Protected exposing (Skybox(..))
import Task exposing (Task)
import WebGL.Texture


{-| Loaded and ready to use equirectangular `Skybox` texture.
-}
type alias Skybox =
Scene3d.Skybox.Protected.Skybox



-- LOADING EQUIRECTANGULAR SKYBOX TEXTURE


{-| Function which defines a task for loading an equirectangular texture,
which can then be applied as a `Scene3d` skybox background.

The first argument is the path (absolute or relative) to the texture.

-}
loadEquirectangular : String -> Task WebGL.Texture.Error Skybox
loadEquirectangular =
Task.map EquirectTexture
<< WebGL.Texture.loadWith
{ magnify = WebGL.Texture.linear
, minify = WebGL.Texture.nearest
, horizontalWrap = WebGL.Texture.clampToEdge
, verticalWrap = WebGL.Texture.clampToEdge
, flipY = True
}
Loading