├── examples ├── .gitignore ├── screenshots │ ├── crate.jpg │ ├── cube.jpg │ ├── thwomp.jpg │ ├── triangle.jpg │ └── first-person.jpg ├── texture │ ├── thwomp-face.jpg │ ├── thwomp-side.jpg │ └── wood-crate.jpg ├── elm-package.json ├── triangle.elm ├── cube.elm ├── intersection.elm ├── thwomp.elm ├── crate.elm └── first-person.elm ├── pipeline.png ├── README.md ├── .gitignore ├── src ├── WebGL │ ├── Settings │ │ ├── Internal.elm │ │ ├── DepthTest.elm │ │ ├── Blend.elm │ │ └── StencilTest.elm │ ├── Settings.elm │ └── Texture.elm ├── Native │ ├── Texture.js │ └── WebGL.js └── WebGL.elm ├── gh-pages.sh ├── CONTRIBUTING.md ├── elm-package.json ├── release.sh ├── LICENSE └── .eslintrc.json /examples/.gitignore: -------------------------------------------------------------------------------- 1 | elm.js 2 | -------------------------------------------------------------------------------- /pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/pipeline.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moved to [elm-explorations/webgl](https://package.elm-lang.org/packages/elm-explorations/webgl/latest) 2 | -------------------------------------------------------------------------------- /examples/screenshots/crate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/examples/screenshots/crate.jpg -------------------------------------------------------------------------------- /examples/screenshots/cube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/examples/screenshots/cube.jpg -------------------------------------------------------------------------------- /examples/screenshots/thwomp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/examples/screenshots/thwomp.jpg -------------------------------------------------------------------------------- /examples/screenshots/triangle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/examples/screenshots/triangle.jpg -------------------------------------------------------------------------------- /examples/texture/thwomp-face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/examples/texture/thwomp-face.jpg -------------------------------------------------------------------------------- /examples/texture/thwomp-side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/examples/texture/thwomp-side.jpg -------------------------------------------------------------------------------- /examples/texture/wood-crate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/examples/texture/wood-crate.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | index.html 3 | .DS_Store 4 | gh-pages 5 | release 6 | node_modules 7 | elm.js 8 | documentation.json 9 | -------------------------------------------------------------------------------- /examples/screenshots/first-person.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-community/webgl/master/examples/screenshots/first-person.jpg -------------------------------------------------------------------------------- /src/WebGL/Settings/Internal.elm: -------------------------------------------------------------------------------- 1 | module WebGL.Settings.Internal exposing (Setting(..)) 2 | 3 | 4 | type Setting 5 | = Blend Int Int Int Int Int Int Float Float Float Float 6 | | DepthTest Int Bool Float Float 7 | | StencilTest Int Int Int Int Int Int Int Int Int Int Int 8 | | Scissor Int Int Int Int 9 | | ColorMask Bool Bool Bool Bool 10 | | CullFace Int 11 | | PolygonOffset Float Float 12 | | SampleCoverage Float Bool 13 | | SampleAlphaToCoverage 14 | -------------------------------------------------------------------------------- /gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rm -rf gh-pages || exit 0; 5 | 6 | mkdir -p gh-pages/examples 7 | 8 | # compile JS using Elm 9 | cd examples 10 | for i in crate cube first-person thwomp triangle; do 11 | elm make $i.elm --yes --output ../gh-pages/examples/$i.html 12 | done 13 | 14 | # copy the textures 15 | cp -R texture ../gh-pages/examples 16 | cp -R screenshots ../gh-pages/examples 17 | 18 | # configure domain 19 | cd ../gh-pages 20 | echo "webgl.elm-community.org" >> CNAME 21 | 22 | # init branch and commit 23 | git init 24 | git add . 25 | git commit -m "Deploying to GH Pages" 26 | git push --force "git@github.com:elm-community/webgl.git" master:gh-pages 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to elm-community/webgl 2 | 3 | This repository is kept for maintenance and does not accept most feature requests. In particular, [#6](https://github.com/elm-community/elm-webgl/issues/6) requires that all 4 | changes be PATCH-level, meaning that the API cannot be modified. 5 | 6 | Any contribution should: 7 | * Compile using `elm make` 8 | * Be recognized as a PATCH change using `elm package diff` 9 | * Not introduce mutable variables 10 | * Justify why the change is needed and why it won't break anything already here 11 | * JavaScript code should be validated using `eslint src` 12 | * Elm code should be formatted with `elm-format` 13 | 14 | Documentation improvements are welcome. 15 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.6", 3 | "summary": "A library for general 3D rendering with WebGL", 4 | "repository": "https://github.com/elm-community/webgl.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "WebGL", 11 | "WebGL.Settings", 12 | "WebGL.Settings.Blend", 13 | "WebGL.Settings.DepthTest", 14 | "WebGL.Settings.StencilTest", 15 | "WebGL.Texture" 16 | ], 17 | "native-modules": true, 18 | "dependencies": { 19 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 20 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 21 | }, 22 | "elm-version": "0.18.0 <= v < 0.19.0" 23 | } 24 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | rm -rf release || exit 0; 5 | 6 | elm-package bump 7 | 8 | version=$(grep -m1 version elm-package.json | awk -F: '{ print $2 }' | sed 's/[", ]//g') 9 | 10 | git commit -a -m "Bump to $version" 11 | git push 12 | 13 | cleanup="examples gh-pages.sh pipeline.png CONTRIBUTING.md .eslintrc.json release.sh" 14 | last_commit=$(git rev-parse HEAD) 15 | 16 | git clone --reference . git@github.com:elm-community/webgl.git release 17 | ( 18 | cd release 19 | git checkout $last_commit 20 | git rm -rf --ignore-unmatch $cleanup 21 | git commit -m "Cleanup and release $version" 22 | git tag -a $version -m "Release $version" 23 | git push origin $version 24 | elm-package publish 25 | ) 26 | -------------------------------------------------------------------------------- /examples/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Examples", 4 | "repository": "https://github.com/elm-community/webgl.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "native-modules": true, 12 | "dependencies": { 13 | "elm-community/linear-algebra": "1.0.0 <= v < 3.0.0", 14 | "elm-lang/animation-frame": "1.0.1 <= v < 2.0.0", 15 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 16 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 17 | "elm-lang/keyboard": "1.0.1 <= v < 2.0.0", 18 | "elm-lang/mouse": "1.0.1 <= v < 2.0.0", 19 | "elm-lang/window": "1.0.1 <= v < 2.0.0" 20 | }, 21 | "elm-version": "0.18.0 <= v < 0.19.0" 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, John P Mayer, Jr 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "rules": { 7 | "indent": [ "error", 2, { "SwitchCase": 1 } ], 8 | "space-infix-ops": [ "error" ], 9 | "linebreak-style": [ "error", "unix" ], 10 | "comma-style": [ "error", "last" ], 11 | "brace-style": [ "error", "1tbs", { "allowSingleLine": true } ], 12 | "curly": "error", 13 | "quotes": [ "error", "single" ], 14 | "semi": [ "error", "always" ], 15 | "space-before-function-paren": [ "error", {"anonymous": "always", "named": "never"} ], 16 | "keyword-spacing": [ "error", { "before": true, "after": true } ], 17 | "space-before-blocks": [ "error", "always" ], 18 | "key-spacing": [ "error", { "beforeColon": false, "afterColon": true, "mode": "strict" } ], 19 | "semi-spacing": [ "error", {"before": false, "after": true} ], 20 | "comma-spacing": [ "error", { "before": false, "after": true } ], 21 | "comma-dangle": ["error", "never"], 22 | "camelcase": "error", 23 | "eqeqeq": "error" 24 | }, 25 | "globals" : { 26 | "_elm_community$webgl$Native_Texture": true, 27 | "_elm_community$webgl$Native_WebGL": true, 28 | "_elm_community$webgl$Native_Settings": true, 29 | "_elm_lang$virtual_dom$Native_VirtualDom": false, 30 | "_elm_lang$core$Native_Utils": false, 31 | "_elm_lang$core$Native_Scheduler": false, 32 | "F2": false, 33 | "F3": false, 34 | "F4": false, 35 | "F5": false, 36 | "F6": false, 37 | "Float32Array": false, 38 | "Int32Array": false, 39 | "Uint16Array": false 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/triangle.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (main) 2 | 3 | {- 4 | Rotating triangle, that is a "hello world" of the WebGL 5 | -} 6 | 7 | import AnimationFrame 8 | import Html exposing (Html) 9 | import Html.Attributes exposing (width, height, style) 10 | import Math.Matrix4 as Mat4 exposing (Mat4) 11 | import Math.Vector3 as Vec3 exposing (vec3, Vec3) 12 | import Time exposing (Time) 13 | import WebGL exposing (Mesh, Shader) 14 | 15 | 16 | main : Program Never Time Time 17 | main = 18 | Html.program 19 | { init = ( 0, Cmd.none ) 20 | , view = view 21 | , subscriptions = (\model -> AnimationFrame.diffs Basics.identity) 22 | , update = (\elapsed currentTime -> ( elapsed + currentTime, Cmd.none )) 23 | } 24 | 25 | 26 | view : Float -> Html msg 27 | view t = 28 | WebGL.toHtml 29 | [ width 400 30 | , height 400 31 | , style [ ( "display", "block" ) ] 32 | ] 33 | [ WebGL.entity 34 | vertexShader 35 | fragmentShader 36 | mesh 37 | { perspective = perspective (t / 1000) } 38 | ] 39 | 40 | 41 | perspective : Float -> Mat4 42 | perspective t = 43 | Mat4.mul 44 | (Mat4.makePerspective 45 1 0.01 100) 45 | (Mat4.makeLookAt (vec3 (4 * cos t) 0 (4 * sin t)) (vec3 0 0 0) (vec3 0 1 0)) 46 | 47 | 48 | 49 | -- Mesh 50 | 51 | 52 | type alias Vertex = 53 | { position : Vec3 54 | , color : Vec3 55 | } 56 | 57 | 58 | mesh : Mesh Vertex 59 | mesh = 60 | WebGL.triangles 61 | [ ( Vertex (vec3 0 0 0) (vec3 1 0 0) 62 | , Vertex (vec3 1 1 0) (vec3 0 1 0) 63 | , Vertex (vec3 1 -1 0) (vec3 0 0 1) 64 | ) 65 | ] 66 | 67 | 68 | 69 | -- Shaders 70 | 71 | 72 | type alias Uniforms = 73 | { perspective : Mat4 } 74 | 75 | 76 | vertexShader : Shader Vertex Uniforms { vcolor : Vec3 } 77 | vertexShader = 78 | [glsl| 79 | 80 | attribute vec3 position; 81 | attribute vec3 color; 82 | uniform mat4 perspective; 83 | varying vec3 vcolor; 84 | 85 | void main () { 86 | gl_Position = perspective * vec4(position, 1.0); 87 | vcolor = color; 88 | } 89 | 90 | |] 91 | 92 | 93 | fragmentShader : Shader {} Uniforms { vcolor : Vec3 } 94 | fragmentShader = 95 | [glsl| 96 | 97 | precision mediump float; 98 | varying vec3 vcolor; 99 | 100 | void main () { 101 | gl_FragColor = vec4(vcolor, 1.0); 102 | } 103 | 104 | |] 105 | -------------------------------------------------------------------------------- /src/Native/Texture.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars, camelcase 2 | var _elm_community$webgl$Native_Texture = function () { 3 | 4 | var NEAREST = 9728; 5 | var LINEAR = 9729; 6 | var CLAMP_TO_EDGE = 33071; 7 | 8 | function guid() { 9 | // eslint-disable-next-line camelcase 10 | return _elm_lang$core$Native_Utils.guid(); 11 | } 12 | 13 | function load(magnify, mininify, horizontalWrap, verticalWrap, flipY, url) { 14 | // eslint-disable-next-line camelcase 15 | var Scheduler = _elm_lang$core$Native_Scheduler; 16 | var isMipmap = mininify !== NEAREST && mininify !== LINEAR; 17 | return Scheduler.nativeBinding(function (callback) { 18 | var img = new Image(); 19 | function createTexture(gl) { 20 | var tex = gl.createTexture(); 21 | gl.bindTexture(gl.TEXTURE_2D, tex); 22 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY); 23 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); 24 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magnify); 25 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mininify); 26 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, horizontalWrap); 27 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, verticalWrap); 28 | if (isMipmap) { 29 | gl.generateMipmap(gl.TEXTURE_2D); 30 | } 31 | gl.bindTexture(gl.TEXTURE_2D, null); 32 | return tex; 33 | } 34 | img.onload = function () { 35 | var width = img.width; 36 | var height = img.height; 37 | var widthPowerOfTwo = (width & (width - 1)) === 0; 38 | var heightPowerOfTwo = (height & (height - 1)) === 0; 39 | var isSizeValid = (widthPowerOfTwo && heightPowerOfTwo) || ( 40 | !isMipmap 41 | && horizontalWrap === CLAMP_TO_EDGE 42 | && verticalWrap === CLAMP_TO_EDGE 43 | ); 44 | if (isSizeValid) { 45 | callback(Scheduler.succeed({ 46 | ctor: 'Texture', 47 | id: guid(), 48 | createTexture: createTexture, 49 | width: width, 50 | height: height 51 | })); 52 | } else { 53 | callback(Scheduler.fail({ 54 | ctor: 'SizeError', 55 | _0: width, 56 | _1: height 57 | })); 58 | } 59 | }; 60 | img.onerror = function () { 61 | callback(Scheduler.fail({ ctor: 'LoadError' })); 62 | }; 63 | if (url.slice(0, 5) !== 'data:') { 64 | img.crossOrigin = 'Anonymous'; 65 | } 66 | img.src = url; 67 | }); 68 | } 69 | 70 | function size(texture) { 71 | // eslint-disable-next-line camelcase 72 | return _elm_lang$core$Native_Utils.Tuple2(texture.width, texture.height); 73 | } 74 | 75 | return { 76 | size: size, 77 | load: F6(load) 78 | }; 79 | 80 | }(); 81 | -------------------------------------------------------------------------------- /examples/cube.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (main) 2 | 3 | {- 4 | Rotating cube with colored sides. 5 | -} 6 | 7 | import AnimationFrame 8 | import Color exposing (Color) 9 | import Html exposing (Html) 10 | import Html.Attributes exposing (width, height, style) 11 | import Math.Matrix4 as Mat4 exposing (Mat4) 12 | import Math.Vector3 as Vec3 exposing (vec3, Vec3) 13 | import Time exposing (Time) 14 | import WebGL exposing (Mesh, Shader) 15 | 16 | 17 | main : Program Never Float Time 18 | main = 19 | Html.program 20 | { init = ( 0, Cmd.none ) 21 | , view = view 22 | , subscriptions = (\_ -> AnimationFrame.diffs Basics.identity) 23 | , update = (\dt theta -> ( theta + dt / 5000, Cmd.none )) 24 | } 25 | 26 | 27 | view : Float -> Html Time 28 | view theta = 29 | WebGL.toHtml 30 | [ width 400 31 | , height 400 32 | , style [ ( "display", "block" ) ] 33 | ] 34 | [ WebGL.entity 35 | vertexShader 36 | fragmentShader 37 | cubeMesh 38 | (uniforms theta) 39 | ] 40 | 41 | 42 | type alias Uniforms = 43 | { rotation : Mat4 44 | , perspective : Mat4 45 | , camera : Mat4 46 | , shade : Float 47 | } 48 | 49 | 50 | uniforms : Float -> Uniforms 51 | uniforms theta = 52 | { rotation = 53 | Mat4.mul 54 | (Mat4.makeRotate (3 * theta) (vec3 0 1 0)) 55 | (Mat4.makeRotate (2 * theta) (vec3 1 0 0)) 56 | , perspective = Mat4.makePerspective 45 1 0.01 100 57 | , camera = Mat4.makeLookAt (vec3 0 0 5) (vec3 0 0 0) (vec3 0 1 0) 58 | , shade = 0.8 59 | } 60 | 61 | 62 | 63 | -- Mesh 64 | 65 | 66 | type alias Vertex = 67 | { color : Vec3 68 | , position : Vec3 69 | } 70 | 71 | 72 | cubeMesh : Mesh Vertex 73 | cubeMesh = 74 | let 75 | rft = 76 | vec3 1 1 1 77 | 78 | lft = 79 | vec3 -1 1 1 80 | 81 | lbt = 82 | vec3 -1 -1 1 83 | 84 | rbt = 85 | vec3 1 -1 1 86 | 87 | rbb = 88 | vec3 1 -1 -1 89 | 90 | rfb = 91 | vec3 1 1 -1 92 | 93 | lfb = 94 | vec3 -1 1 -1 95 | 96 | lbb = 97 | vec3 -1 -1 -1 98 | in 99 | [ face Color.green rft rfb rbb rbt 100 | , face Color.blue rft rfb lfb lft 101 | , face Color.yellow rft lft lbt rbt 102 | , face Color.red rfb lfb lbb rbb 103 | , face Color.purple lft lfb lbb lbt 104 | , face Color.orange rbt rbb lbb lbt 105 | ] 106 | |> List.concat 107 | |> WebGL.triangles 108 | 109 | 110 | face : Color -> Vec3 -> Vec3 -> Vec3 -> Vec3 -> List ( Vertex, Vertex, Vertex ) 111 | face rawColor a b c d = 112 | let 113 | color = 114 | let 115 | c = 116 | Color.toRgb rawColor 117 | in 118 | vec3 119 | (toFloat c.red / 255) 120 | (toFloat c.green / 255) 121 | (toFloat c.blue / 255) 122 | 123 | vertex position = 124 | Vertex color position 125 | in 126 | [ ( vertex a, vertex b, vertex c ) 127 | , ( vertex c, vertex d, vertex a ) 128 | ] 129 | 130 | 131 | 132 | -- Shaders 133 | 134 | 135 | vertexShader : Shader Vertex Uniforms { vcolor : Vec3 } 136 | vertexShader = 137 | [glsl| 138 | 139 | attribute vec3 position; 140 | attribute vec3 color; 141 | uniform mat4 perspective; 142 | uniform mat4 camera; 143 | uniform mat4 rotation; 144 | varying vec3 vcolor; 145 | void main () { 146 | gl_Position = perspective * camera * rotation * vec4(position, 1.0); 147 | vcolor = color; 148 | } 149 | 150 | |] 151 | 152 | 153 | fragmentShader : Shader {} Uniforms { vcolor : Vec3 } 154 | fragmentShader = 155 | [glsl| 156 | 157 | precision mediump float; 158 | uniform float shade; 159 | varying vec3 vcolor; 160 | void main () { 161 | gl_FragColor = shade * vec4(vcolor, 1.0); 162 | } 163 | 164 | |] 165 | -------------------------------------------------------------------------------- /examples/intersection.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (main) 2 | 3 | {- 4 | Draws a red and a green triangles, where the green triangle is only 5 | visible in the intercection with the red triangle. 6 | A green outline marks a hidden area of the green triangle. 7 | 8 | This example helps to understand the separate stencil test. 9 | -} 10 | 11 | import Html exposing (Html) 12 | import Html.Attributes exposing (width, height, style) 13 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 14 | import WebGL exposing (Shader, Mesh) 15 | import WebGL.Settings exposing (Setting) 16 | import WebGL.Settings.StencilTest as StencilTest 17 | 18 | 19 | main : Html () 20 | main = 21 | WebGL.toHtmlWith 22 | [ WebGL.stencil 0 ] 23 | [ width 400 24 | , height 400 25 | , style [ ( "display", "block" ) ] 26 | ] 27 | [ WebGL.entityWith 28 | [ stencilTest ] 29 | vertexShader 30 | fragmentShader 31 | redAndGreenTriangles 32 | {} 33 | , WebGL.entity 34 | vertexShader 35 | fragmentShader 36 | greenOutline 37 | {} 38 | ] 39 | 40 | 41 | {-| Rendering with the following setting will write 1's to the stencil buffer 42 | for all front-facing triangles, and then test the subsequent back-facing 43 | triangles against the stencil buffer, so they be rendered only for the areas 44 | that are marked with 1's in the stencil buffer. 45 | 46 | `StencilTest.testSeparate` takes two options, one for front-, and another for 47 | back-facing triangles: 48 | 49 | * The front-facing stencil test always passes, and replaces the stencil buffer 50 | with 1. 51 | * The back-facing stencil test only passes when the value in the stencil buffer 52 | is equal to 1. It does not modify the stencil buffer. 53 | -} 54 | stencilTest : Setting 55 | stencilTest = 56 | StencilTest.testSeparate 57 | { ref = 1 58 | , mask = 0xFF 59 | , writeMask = 0xFF 60 | } 61 | { test = StencilTest.always 62 | , fail = StencilTest.keep 63 | , zfail = StencilTest.keep 64 | , zpass = StencilTest.replace 65 | } 66 | { test = StencilTest.equal 67 | , fail = StencilTest.keep 68 | , zfail = StencilTest.keep 69 | , zpass = StencilTest.keep 70 | } 71 | 72 | 73 | 74 | -- Mesh 75 | 76 | 77 | type alias Vertex = 78 | { position : Vec3 79 | , color : Vec3 80 | } 81 | 82 | 83 | redAndGreenTriangles : Mesh Vertex 84 | redAndGreenTriangles = 85 | let 86 | -- the red triangle is front-facing 87 | redTriangle = 88 | ( Vertex (vec3 -1 0.5 0) (vec3 1 0 0) 89 | , Vertex (vec3 0 -0.5 0) (vec3 1 0 0) 90 | , Vertex (vec3 1 0.5 0) (vec3 1 0 0) 91 | ) 92 | 93 | -- the green triangle is back-facing 94 | greenTriangle = 95 | ( Vertex (vec3 -1 -0.5 0) (vec3 0 1 0) 96 | , Vertex (vec3 0 0.5 0) (vec3 0 1 0) 97 | , Vertex (vec3 1 -0.5 0) (vec3 0 1 0) 98 | ) 99 | in 100 | WebGL.triangles 101 | [ redTriangle 102 | , greenTriangle 103 | ] 104 | 105 | 106 | greenOutline : Mesh Vertex 107 | greenOutline = 108 | WebGL.lineLoop 109 | [ Vertex (vec3 -1 -0.5 0) (vec3 0 1 0) 110 | , Vertex (vec3 0 0.5 0) (vec3 0 1 0) 111 | , Vertex (vec3 1 -0.5 0) (vec3 0 1 0) 112 | ] 113 | 114 | 115 | 116 | -- Shaders 117 | 118 | 119 | vertexShader : Shader Vertex {} { vcolor : Vec3 } 120 | vertexShader = 121 | [glsl| 122 | 123 | attribute vec3 position; 124 | attribute vec3 color; 125 | varying vec3 vcolor; 126 | 127 | void main () { 128 | gl_Position = vec4(position, 1.0); 129 | vcolor = color; 130 | } 131 | 132 | |] 133 | 134 | 135 | fragmentShader : Shader {} {} { vcolor : Vec3 } 136 | fragmentShader = 137 | [glsl| 138 | 139 | precision mediump float; 140 | varying vec3 vcolor; 141 | 142 | void main () { 143 | gl_FragColor = vec4(vcolor, 1.0); 144 | } 145 | 146 | |] 147 | -------------------------------------------------------------------------------- /src/WebGL/Settings/DepthTest.elm: -------------------------------------------------------------------------------- 1 | module WebGL.Settings.DepthTest 2 | exposing 3 | ( default 4 | , Options 5 | , less 6 | , never 7 | , always 8 | , equal 9 | , greater 10 | , notEqual 11 | , lessOrEqual 12 | , greaterOrEqual 13 | ) 14 | 15 | {-| You can read more about depth-testing in the 16 | [OpenGL wiki](https://www.khronos.org/opengl/wiki/Depth_Test) 17 | or [OpenGL docs](https://www.opengl.org/sdk/docs/man2/xhtml/glDepthFunc.xml). 18 | 19 | # Depth Test 20 | @docs default 21 | 22 | # Custom Tests 23 | @docs Options, less, never, always, equal, greater, notEqual, 24 | lessOrEqual, greaterOrEqual 25 | -} 26 | 27 | import WebGL.Settings exposing (Setting) 28 | import WebGL.Settings.Internal as I 29 | 30 | 31 | {-| With every pixel, we have to figure out which color to show. 32 | 33 | Imagine you have many entities in the same line of sight. The floor, 34 | then a table, then a plate. When depth-testing is off, you go through 35 | the entities in the order they appear in your *code*! That means if 36 | you describe the floor last, it will be “on top” of the table and plate. 37 | 38 | Depth-testing means the color is chosen based on the distance from the 39 | camera. So `default` uses the color closest to the camera. This means 40 | the plate will be on top of the table, and both are on top of the floor. 41 | Seems more reasonable! 42 | 43 | There are a bunch of ways you can customize the depth test, shown later, 44 | and you can use them to define `default` like this: 45 | 46 | default = 47 | less { write = True, near = 0, far = 1 } 48 | 49 | Requires [`WebGL.depth`](WebGL#depth) option in 50 | [`toHtmlWith`](WebGL#toHtmlWith). 51 | 52 | -} 53 | default : Setting 54 | default = 55 | less { write = True, near = 0, far = 1 } 56 | 57 | 58 | {-| When rendering, you have a buffer of pixels. Depth-testing works by 59 | creating a second buffer with exactly the same number of entries, but 60 | instead of holding colors, each entry holds the distance from the camera. 61 | You go through all your entities, writing into the depth buffer, and then 62 | you draw the color of the “winner”. 63 | 64 | Which color wins? This is based on a bunch of comparison functions: 65 | 66 | less options -- value < depth 67 | never options -- Never pass 68 | always options -- Always pass 69 | equal options -- value == depth 70 | greater options -- value > depth 71 | notEqual options -- value != depth 72 | lessOrEqual options -- value <= depth 73 | greaterOrEqual options -- value >= depth 74 | 75 | If the test passes, the current value will be written into the depth buffer, so 76 | the next pixels will be tested against it. Sometimes you may want to disable 77 | writing. For example, when using depth test together with stencil test to create 78 | [reflection effect](https://open.gl/depthstencils) you want to draw the 79 | reflection *underneath* the floor, in this case you set `write = False` 80 | when drawing the floor. The 81 | [crate example](https://github.com/elm-community/webgl/blob/master/examples/crate.elm) 82 | shows how to do it in Elm. 83 | 84 | `near` and `far` allow to allocate a portion of the depth range from 0 to 1. 85 | For example, if you want to render GUI on top of the scene, you can 86 | set `near = 0.1, far = 1` for the scene and then render the GUI with 87 | `near = 0, far = 0.1`. 88 | -} 89 | type alias Options = 90 | { write : Bool 91 | , near : Float 92 | , far : Float 93 | } 94 | 95 | 96 | {-| -} 97 | less : Options -> Setting 98 | less { write, near, far } = 99 | I.DepthTest 513 write near far 100 | 101 | 102 | {-| -} 103 | never : Options -> Setting 104 | never { write, near, far } = 105 | I.DepthTest 512 write near far 106 | 107 | 108 | {-| -} 109 | always : Options -> Setting 110 | always { write, near, far } = 111 | I.DepthTest 519 write near far 112 | 113 | 114 | {-| -} 115 | equal : Options -> Setting 116 | equal { write, near, far } = 117 | I.DepthTest 514 write near far 118 | 119 | 120 | {-| -} 121 | greater : Options -> Setting 122 | greater { write, near, far } = 123 | I.DepthTest 516 write near far 124 | 125 | 126 | {-| -} 127 | notEqual : Options -> Setting 128 | notEqual { write, near, far } = 129 | I.DepthTest 517 write near far 130 | 131 | 132 | {-| -} 133 | lessOrEqual : Options -> Setting 134 | lessOrEqual { write, near, far } = 135 | I.DepthTest 515 write near far 136 | 137 | 138 | {-| -} 139 | greaterOrEqual : Options -> Setting 140 | greaterOrEqual { write, near, far } = 141 | I.DepthTest 518 write near far 142 | -------------------------------------------------------------------------------- /src/WebGL/Settings.elm: -------------------------------------------------------------------------------- 1 | module WebGL.Settings 2 | exposing 3 | ( Setting 4 | , scissor 5 | , colorMask 6 | , polygonOffset 7 | , sampleCoverage 8 | , sampleAlphaToCoverage 9 | , cullFace 10 | , FaceMode 11 | , front 12 | , back 13 | , frontAndBack 14 | ) 15 | 16 | {-| # Settings 17 | @docs Setting, scissor, colorMask, polygonOffset, sampleAlphaToCoverage, 18 | sampleCoverage, cullFace 19 | 20 | ## Face Modes 21 | @docs FaceMode, front, back, frontAndBack 22 | -} 23 | 24 | import WebGL.Settings.Internal as I 25 | 26 | 27 | {-| Lets you customize how an [`Entity`](WebGL#Entity) is rendered. So if you 28 | only want to see the red part of your entity, you would use 29 | [`entityWith`](WebGL#entityWith) and [`colorMask`](#colorMask) to say: 30 | 31 | entityWith [colorMask True False False False] 32 | vertShader fragShader mesh uniforms 33 | 34 | -- vertShader : Shader attributes uniforms varyings 35 | -- fragShader : Shader {} uniforms varyings 36 | -- mesh : Mesh attributes 37 | -- uniforms : uniforms 38 | 39 | -} 40 | type alias Setting = 41 | I.Setting 42 | 43 | 44 | {-| Set the scissor box, which limits the drawing of fragments to the 45 | screen to a specified rectangle. 46 | 47 | The arguments are the coordinates of the lower left corner, width and height. 48 | -} 49 | scissor : Int -> Int -> Int -> Int -> Setting 50 | scissor = 51 | I.Scissor 52 | 53 | 54 | {-| Specify whether or not each channel (red, green, blue, alpha) should be 55 | output on the screen. 56 | -} 57 | colorMask : Bool -> Bool -> Bool -> Bool -> Setting 58 | colorMask = 59 | I.ColorMask 60 | 61 | 62 | {-| When you want to draw the highlighting wireframe on top of the solid 63 | object, the lines may fade in and out of the coincident polygons, 64 | which is sometimes called "stitching" and is visually unpleasant. 65 | 66 | [Polygon Offset](http://www.glprogramming.com/red/chapter06.html#name4) 67 | helps to avoid "stitching" by adding an offset to pixel’s depth 68 | values before the depth test is performed and before the value is written 69 | into the depth buffer. 70 | 71 | polygonOffset factor units 72 | 73 | This adds an `offset = m * factor + r * units`, where 74 | 75 | * `m = max (dz / dx) (dz / dy)` is the maximum depth slope of the polygon. 76 | The depth slope is the change in `z` (depth) values divided by the change in 77 | either `x` or `y` coordinates, as you traverse a polygon; 78 | * `r` is the smallest value guaranteed to produce a resolvable difference in 79 | window coordinate depth values. The value `r` is an implementation-specific 80 | constant. 81 | 82 | The question is: "How much offset is enough?". It really depends on the slope. 83 | For polygons that are parallel to the near and far clipping planes, 84 | the depth slope is zero, so the minimum offset is needed: 85 | 86 | polygonOffset 0 1 87 | 88 | For polygons that are at a great angle to the clipping planes, the depth slope 89 | can be significantly greater than zero. Use small non-zero values for factor, 90 | such as `0.75` or `1.0` should be enough to generate distinct depth values: 91 | 92 | polygonOffset 0.75 1 93 | -} 94 | polygonOffset : Float -> Float -> Setting 95 | polygonOffset = 96 | I.PolygonOffset 97 | 98 | 99 | {-| When you render overlapping transparent entities, like grass or hair, you 100 | may notice that alpha blending doesn’t really work with depth testing, because 101 | depth test ignores transparency. 102 | [Alpha To Coverage](http://wiki.polycount.com/wiki/Transparency_map#Alpha_To_Coverage) 103 | is a way to address this issue without sorting transparent entities. 104 | 105 | It works by computing a temporary coverage value, where each bit is determined 106 | by the alpha value at the corresponding sample location. The temporary coverage 107 | value is then ANDed with the fragment coverage value. 108 | 109 | Requires [`WebGL.antialias`](WebGL#antialias) option. 110 | -} 111 | sampleAlphaToCoverage : Setting 112 | sampleAlphaToCoverage = 113 | I.SampleAlphaToCoverage 114 | 115 | 116 | {-| Specifies multisample coverage parameters. The fragment's coverage is ANDed 117 | with the temporary coverage value. 118 | 119 | * the first argument specifies sample coverage value, that is clamped to the 120 | range from 0 to 1; 121 | * the second argument represents if the coverage masks should be inverted. 122 | 123 | Requires [`WebGL.antialias`](WebGL#antialias) option. 124 | -} 125 | sampleCoverage : Float -> Bool -> Setting 126 | sampleCoverage = 127 | I.SampleCoverage 128 | 129 | 130 | {-| Excludes polygons based on winding (the order of the vertices) in window 131 | coordinates. Polygons with counter-clock-wise winding are front-facing. 132 | -} 133 | cullFace : FaceMode -> Setting 134 | cullFace (FaceMode faceMode) = 135 | I.CullFace faceMode 136 | 137 | 138 | {-| Targets the polygons based on their facing. 139 | -} 140 | type FaceMode 141 | = FaceMode Int 142 | 143 | 144 | {-| -} 145 | front : FaceMode 146 | front = 147 | FaceMode 1028 148 | 149 | 150 | {-| -} 151 | back : FaceMode 152 | back = 153 | FaceMode 1029 154 | 155 | 156 | {-| -} 157 | frontAndBack : FaceMode 158 | frontAndBack = 159 | FaceMode 1032 160 | -------------------------------------------------------------------------------- /src/WebGL/Settings/Blend.elm: -------------------------------------------------------------------------------- 1 | module WebGL.Settings.Blend 2 | exposing 3 | ( add 4 | , subtract 5 | , reverseSubtract 6 | , Factor 7 | , zero 8 | , one 9 | , srcColor 10 | , oneMinusSrcColor 11 | , dstColor 12 | , oneMinusDstColor 13 | , srcAlpha 14 | , oneMinusSrcAlpha 15 | , dstAlpha 16 | , oneMinusDstAlpha 17 | , srcAlphaSaturate 18 | , custom 19 | , Blender 20 | , customAdd 21 | , customSubtract 22 | , customReverseSubtract 23 | , constantColor 24 | , oneMinusConstantColor 25 | , constantAlpha 26 | , oneMinusConstantAlpha 27 | ) 28 | 29 | {-| 30 | # Blenders 31 | @docs add, subtract, reverseSubtract 32 | 33 | # Blend Factors 34 | @docs Factor, zero, one, srcColor, oneMinusSrcColor, dstColor, 35 | oneMinusDstColor, srcAlpha, oneMinusSrcAlpha, dstAlpha, 36 | oneMinusDstAlpha, srcAlphaSaturate 37 | 38 | # Custom Blenders 39 | @docs custom, Blender, customAdd, customSubtract, customReverseSubtract, 40 | constantColor, oneMinusConstantColor, constantAlpha, 41 | oneMinusConstantAlpha 42 | -} 43 | 44 | import WebGL.Settings exposing (Setting) 45 | import WebGL.Settings.Internal as I 46 | 47 | 48 | {-| Add the color of the current `Renderable` (the source color) 49 | with whatever is behind it (the destination color). For example, 50 | here is the “default” blender: 51 | 52 | add one zero 53 | 54 | The resulting color will be `(src * 1) + (dest * 0)`, which means 55 | we do not use the destination color at all! 56 | You can get a feel for all the different blending factors 57 | [here](https://threejs.org/examples/webgl_materials_blending_custom.html). 58 | -} 59 | add : Factor -> Factor -> Setting 60 | add factor1 factor2 = 61 | custom 62 | { r = 0 63 | , g = 0 64 | , b = 0 65 | , a = 0 66 | , color = customAdd factor1 factor2 67 | , alpha = customAdd factor1 factor2 68 | } 69 | 70 | 71 | {-| Similar to [`add`](#add), but it does `(src * factor1) - (dest * factor2)`. 72 | For example: 73 | 74 | subtract one one 75 | 76 | This would do `(src * 1) - (dest * 1)` so you would take away colors 77 | based on the background. 78 | -} 79 | subtract : Factor -> Factor -> Setting 80 | subtract factor1 factor2 = 81 | custom 82 | { r = 0 83 | , g = 0 84 | , b = 0 85 | , a = 0 86 | , color = customSubtract factor1 factor2 87 | , alpha = customSubtract factor1 factor2 88 | } 89 | 90 | 91 | {-| Similar to [`add`](#add), but it does `(dest * factor2) - (src * factor1)`. 92 | This one is weird. 93 | -} 94 | reverseSubtract : Factor -> Factor -> Setting 95 | reverseSubtract factor1 factor2 = 96 | custom 97 | { r = 0 98 | , g = 0 99 | , b = 0 100 | , a = 0 101 | , color = customReverseSubtract factor1 factor2 102 | , alpha = customReverseSubtract factor1 factor2 103 | } 104 | 105 | 106 | {-| -} 107 | type Factor 108 | = Factor Int 109 | 110 | 111 | {-| -} 112 | zero : Factor 113 | zero = 114 | Factor 0 115 | 116 | 117 | {-| -} 118 | one : Factor 119 | one = 120 | Factor 1 121 | 122 | 123 | {-| -} 124 | srcColor : Factor 125 | srcColor = 126 | Factor 768 127 | 128 | 129 | {-| -} 130 | oneMinusSrcColor : Factor 131 | oneMinusSrcColor = 132 | Factor 769 133 | 134 | 135 | {-| -} 136 | dstColor : Factor 137 | dstColor = 138 | Factor 774 139 | 140 | 141 | {-| -} 142 | oneMinusDstColor : Factor 143 | oneMinusDstColor = 144 | Factor 775 145 | 146 | 147 | {-| -} 148 | srcAlpha : Factor 149 | srcAlpha = 150 | Factor 770 151 | 152 | 153 | {-| -} 154 | oneMinusSrcAlpha : Factor 155 | oneMinusSrcAlpha = 156 | Factor 771 157 | 158 | 159 | {-| -} 160 | dstAlpha : Factor 161 | dstAlpha = 162 | Factor 772 163 | 164 | 165 | {-| -} 166 | oneMinusDstAlpha : Factor 167 | oneMinusDstAlpha = 168 | Factor 773 169 | 170 | 171 | {-| -} 172 | srcAlphaSaturate : Factor 173 | srcAlphaSaturate = 174 | Factor 776 175 | 176 | 177 | 178 | -- BLENDING WITH CONSTANT COLORS 179 | 180 | 181 | {-| It is possible to do some very fancy blending with 182 | `custom`. For example, you can blend the color value and 183 | the alpha values separately: 184 | 185 | myBlender : Float -> Setting 186 | myBlender alpha = 187 | custom 188 | { r = 0 189 | , g = 0 190 | , b = 0 191 | , a = alpha 192 | , color = customAdd one zero 193 | , alpha = customAdd one constantAlpha 194 | } 195 | -} 196 | custom : 197 | { r : Float 198 | , g : Float 199 | , b : Float 200 | , a : Float 201 | , color : Blender 202 | , alpha : Blender 203 | } 204 | -> Setting 205 | custom { r, g, b, a, color, alpha } = 206 | let 207 | expand (Blender eq1 f11 f12) (Blender eq2 f21 f22) = 208 | I.Blend eq1 f11 f12 eq2 f21 f22 r g b a 209 | in 210 | expand color alpha 211 | 212 | 213 | {-| A `Blender` mixes the color of the current `Entity` (the source color) 214 | with whatever is behind it (the destination color). 215 | You can get a feel for all the options [here](https://threejs.org/examples/webgl_materials_blending_custom.html). 216 | -} 217 | type Blender 218 | = Blender Int Int Int 219 | 220 | 221 | {-| -} 222 | customAdd : Factor -> Factor -> Blender 223 | customAdd (Factor factor1) (Factor factor2) = 224 | Blender 32774 factor1 factor2 225 | 226 | 227 | {-| -} 228 | customSubtract : Factor -> Factor -> Blender 229 | customSubtract (Factor factor1) (Factor factor2) = 230 | Blender 32778 factor1 factor2 231 | 232 | 233 | {-| -} 234 | customReverseSubtract : Factor -> Factor -> Blender 235 | customReverseSubtract (Factor factor1) (Factor factor2) = 236 | Blender 32779 factor1 factor2 237 | 238 | 239 | {-| This uses the constant `r`, `g`, `b`, and `a` values 240 | given to [`custom`](#custom). If you use this `Factor` with 241 | [`add`](#add), the constant color will default to black. 242 | 243 | Because of 244 | [restriction in WebGL](https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.13), 245 | you cannot create a `Blender`, that has one factor set to 246 | `constantColor` or `oneMinusConstantColor` and another set to 247 | `constantAlpha` or `oneMinusConstantAlpha`. 248 | -} 249 | constantColor : Factor 250 | constantColor = 251 | Factor 32769 252 | 253 | 254 | {-| -} 255 | oneMinusConstantColor : Factor 256 | oneMinusConstantColor = 257 | Factor 32770 258 | 259 | 260 | {-| -} 261 | constantAlpha : Factor 262 | constantAlpha = 263 | Factor 32771 264 | 265 | 266 | {-| -} 267 | oneMinusConstantAlpha : Factor 268 | oneMinusConstantAlpha = 269 | Factor 32772 270 | -------------------------------------------------------------------------------- /src/WebGL/Settings/StencilTest.elm: -------------------------------------------------------------------------------- 1 | module WebGL.Settings.StencilTest 2 | exposing 3 | ( test 4 | , testSeparate 5 | , Test 6 | , always 7 | , equal 8 | , never 9 | , less 10 | , greater 11 | , lessOrEqual 12 | , greaterOrEqual 13 | , notEqual 14 | , Operation 15 | , replace 16 | , keep 17 | , zero 18 | , increment 19 | , decrement 20 | , invert 21 | , incrementWrap 22 | , decrementWrap 23 | ) 24 | 25 | {-| You can read more about stencil-testing in the 26 | [OpenGL wiki](https://www.khronos.org/opengl/wiki/Stencil_Test) 27 | or [OpenGL docs](https://www.opengl.org/sdk/docs/man2/xhtml/glStencilFunc.xml). 28 | 29 | # Stencil Test 30 | @docs test 31 | 32 | ## Tests 33 | @docs Test, always, equal, never, less, greater, notEqual, 34 | lessOrEqual, greaterOrEqual 35 | 36 | ## Operations 37 | @docs Operation, replace, keep, zero, increment, decrement, invert, 38 | incrementWrap, decrementWrap 39 | 40 | # Separate Test 41 | @docs testSeparate 42 | -} 43 | 44 | import WebGL.Settings exposing (Setting) 45 | import WebGL.Settings.Internal as I 46 | 47 | 48 | {-| When you need to draw an intercection of two entities, e.g. a reflection in 49 | the mirror, you can test against the stencil buffer, that has to be enabled 50 | with [`stencil`](WebGL#stencil) option in [`toHtmlWith`](WebGL#toHtmlWith). 51 | 52 | Stencil test decides if the pixel should be drawn on the screen. 53 | Depending on the results, it performs one of the following 54 | [operations](#Operation) on the stencil buffer: 55 | 56 | * `fail`—the operation to use when the stencil test fails; 57 | * `zfail`—the operation to use when the stencil test passes, but the depth 58 | test fails; 59 | * `zpass`—the operation to use when both the stencil test and the depth test 60 | pass, or when the stencil test passes and there is no depth buffer or depth 61 | testing is disabled. 62 | 63 | For example, draw the mirror `Entity` on the screen and fill the stencil buffer 64 | with all 1's: 65 | 66 | test 67 | { ref = 1 68 | , mask = 0xFF 69 | , test = always -- pass for each pixel 70 | , fail = keep -- noop 71 | , zfail = keep -- noop 72 | , zpass = replace -- write ref to the stencil buffer 73 | , writeMask = 0xFF -- enable all stencil bits for writing 74 | } 75 | 76 | Crop the reflection `Entity` using the values from the stencil buffer: 77 | 78 | test 79 | { ref = 1 80 | , mask = 0xFF 81 | , test = equal -- pass when the stencil value is equal to ref = 1 82 | , fail = keep -- noop 83 | , zfail = keep -- noop 84 | , zpass = keep -- noop 85 | , writeMask = 0 -- disable writing to the stencil buffer 86 | } 87 | 88 | You can see the complete example 89 | [here](https://github.com/elm-community/webgl/blob/master/examples/crate.elm). 90 | -} 91 | test : 92 | { ref : Int 93 | , mask : Int 94 | , test : Test 95 | , fail : Operation 96 | , zfail : Operation 97 | , zpass : Operation 98 | , writeMask : Int 99 | } 100 | -> Setting 101 | test { ref, mask, test, fail, zfail, zpass, writeMask } = 102 | testSeparate 103 | { ref = ref, mask = mask, writeMask = writeMask } 104 | { test = test, fail = fail, zfail = zfail, zpass = zpass } 105 | { test = test, fail = fail, zfail = zfail, zpass = zpass } 106 | 107 | 108 | {-| The `Test` allows you to define how to compare the reference value 109 | with the stencil buffer value, in order to set the conditions under which 110 | the pixel will be drawn. 111 | 112 | always -- Always pass 113 | equal -- ref & mask == stencil & mask 114 | never -- Never pass 115 | less -- ref & mask < stencil & mask 116 | greater -- ref & mask > stencil & mask 117 | notEqual -- ref & mask != stencil & mask 118 | lessOrEqual -- ref & mask <= stencil & mask 119 | greaterOrEqual -- ref & mask >= stencil & mask 120 | -} 121 | type Test 122 | = Test Int 123 | 124 | 125 | {-| -} 126 | always : Test 127 | always = 128 | Test 519 129 | 130 | 131 | {-| -} 132 | equal : Test 133 | equal = 134 | Test 514 135 | 136 | 137 | {-| -} 138 | never : Test 139 | never = 140 | Test 512 141 | 142 | 143 | {-| -} 144 | less : Test 145 | less = 146 | Test 513 147 | 148 | 149 | {-| -} 150 | greater : Test 151 | greater = 152 | Test 516 153 | 154 | 155 | {-| -} 156 | notEqual : Test 157 | notEqual = 158 | Test 517 159 | 160 | 161 | {-| -} 162 | lessOrEqual : Test 163 | lessOrEqual = 164 | Test 515 165 | 166 | 167 | {-| -} 168 | greaterOrEqual : Test 169 | greaterOrEqual = 170 | Test 518 171 | 172 | 173 | {-| Defines how to update the value in the stencil buffer. 174 | -} 175 | type Operation 176 | = Operation Int 177 | 178 | 179 | {-| Sets the stencil buffer value to `ref` from the stencil test. 180 | -} 181 | replace : Operation 182 | replace = 183 | Operation 7681 184 | 185 | 186 | {-| Keeps the current stencil buffer value. Use this as a noop. 187 | -} 188 | keep : Operation 189 | keep = 190 | Operation 7680 191 | 192 | 193 | {-| Sets the stencil buffer value to 0. 194 | -} 195 | zero : Operation 196 | zero = 197 | Operation 0 198 | 199 | 200 | {-| Increments the current stencil buffer value. Clamps to the maximum 201 | representable unsigned value. 202 | -} 203 | increment : Operation 204 | increment = 205 | Operation 7682 206 | 207 | 208 | {-| Decrements the current stencil buffer value. Clamps to 0. 209 | -} 210 | decrement : Operation 211 | decrement = 212 | Operation 7683 213 | 214 | 215 | {-| Bitwise inverts the current stencil buffer value. 216 | -} 217 | invert : Operation 218 | invert = 219 | Operation 5386 220 | 221 | 222 | {-| Increments the current stencil buffer value. Wraps stencil buffer value to 223 | zero when incrementing the maximum representable unsigned value. 224 | -} 225 | incrementWrap : Operation 226 | incrementWrap = 227 | Operation 34055 228 | 229 | 230 | {-| Decrements the current stencil buffer value. 231 | Wraps stencil buffer value to the maximum representable unsigned 232 | value when decrementing a stencil buffer value of zero. 233 | -} 234 | decrementWrap : Operation 235 | decrementWrap = 236 | Operation 34056 237 | 238 | 239 | {-| Different options for front and back facing polygons. 240 | -} 241 | testSeparate : 242 | { ref : Int, mask : Int, writeMask : Int } 243 | -> { test : Test, fail : Operation, zfail : Operation, zpass : Operation } 244 | -> { test : Test, fail : Operation, zfail : Operation, zpass : Operation } 245 | -> Setting 246 | testSeparate { ref, mask, writeMask } options1 options2 = 247 | let 248 | expandTest (Test test) fn = 249 | fn test 250 | 251 | expandOp (Operation op) fn = 252 | fn op 253 | 254 | expand { test, fail, zfail, zpass } = 255 | expandTest test 256 | >> expandOp fail 257 | >> expandOp zfail 258 | >> expandOp zpass 259 | in 260 | I.StencilTest ref mask writeMask 261 | |> expand options1 262 | |> expand options2 263 | -------------------------------------------------------------------------------- /examples/thwomp.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (main) 2 | 3 | {- 4 | Thanks to The PaperNES Guy for the texture: 5 | http://the-papernes-guy.deviantart.com/art/Thwomps-Thwomps-Thwomps-186879685 6 | -} 7 | 8 | import Html exposing (Html, text) 9 | import Html.Attributes exposing (width, height, style) 10 | import Math.Matrix4 as Mat4 exposing (Mat4) 11 | import Math.Vector2 as Vec2 exposing (Vec2, vec2) 12 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 13 | import Mouse 14 | import Task exposing (Task) 15 | import WebGL exposing (Mesh, Shader, Entity) 16 | import WebGL.Texture as Texture exposing (Texture, defaultOptions, Error) 17 | import Window 18 | 19 | 20 | type alias Model = 21 | { size : Window.Size 22 | , position : Mouse.Position 23 | , textures : Maybe ( Texture, Texture ) 24 | } 25 | 26 | 27 | type Action 28 | = TexturesError Error 29 | | TexturesLoaded ( Texture, Texture ) 30 | | Resize Window.Size 31 | | MouseMove Mouse.Position 32 | 33 | 34 | main : Program Never Model Action 35 | main = 36 | Html.program 37 | { init = init 38 | , view = view 39 | , subscriptions = subscriptions 40 | , update = update 41 | } 42 | 43 | 44 | init : ( Model, Cmd Action ) 45 | init = 46 | ( { size = Window.Size 0 0 47 | , position = Mouse.Position 0 0 48 | , textures = Nothing 49 | } 50 | , Cmd.batch 51 | [ Task.perform Resize Window.size 52 | , fetchTextures 53 | ] 54 | ) 55 | 56 | 57 | subscriptions : Model -> Sub Action 58 | subscriptions _ = 59 | Sub.batch 60 | [ Window.resizes Resize 61 | , Mouse.moves MouseMove 62 | ] 63 | 64 | 65 | update : Action -> Model -> ( Model, Cmd Action ) 66 | update action model = 67 | case action of 68 | TexturesError err -> 69 | ( model, Cmd.none ) 70 | 71 | TexturesLoaded textures -> 72 | ( { model | textures = Just textures }, Cmd.none ) 73 | 74 | Resize size -> 75 | ( { model | size = size }, Cmd.none ) 76 | 77 | MouseMove position -> 78 | ( { model | position = position }, Cmd.none ) 79 | 80 | 81 | fetchTextures : Cmd Action 82 | fetchTextures = 83 | [ "texture/thwomp-face.jpg" 84 | , "texture/thwomp-side.jpg" 85 | ] 86 | |> List.map 87 | (Texture.loadWith 88 | { defaultOptions 89 | | magnify = Texture.nearest 90 | , minify = Texture.nearest 91 | } 92 | ) 93 | |> Task.sequence 94 | |> Task.andThen 95 | (\textures -> 96 | case textures of 97 | face :: side :: _ -> 98 | Task.succeed ( face, side ) 99 | 100 | _ -> 101 | Task.fail Texture.LoadError 102 | ) 103 | |> Task.attempt 104 | (\result -> 105 | case result of 106 | Err error -> 107 | TexturesError error 108 | 109 | Ok textures -> 110 | TexturesLoaded textures 111 | ) 112 | 113 | 114 | 115 | -- Meshes 116 | 117 | 118 | type alias Vertex = 119 | { position : Vec3 120 | , coord : Vec2 121 | } 122 | 123 | 124 | faceMesh : Mesh Vertex 125 | faceMesh = 126 | WebGL.triangles square 127 | 128 | 129 | sidesMesh : Mesh Vertex 130 | sidesMesh = 131 | [ ( 90, 0 ), ( 180, 0 ), ( 270, 0 ), ( 0, 90 ), ( 0, 270 ) ] 132 | |> List.concatMap rotatedSquare 133 | |> WebGL.triangles 134 | 135 | 136 | rotatedSquare : ( Float, Float ) -> List ( Vertex, Vertex, Vertex ) 137 | rotatedSquare ( angleXZ, angleYZ ) = 138 | let 139 | transformMat = 140 | Mat4.mul 141 | (Mat4.makeRotate (degrees angleXZ) Vec3.j) 142 | (Mat4.makeRotate (degrees angleYZ) Vec3.i) 143 | 144 | transform vertex = 145 | { vertex 146 | | position = 147 | Mat4.transform transformMat vertex.position 148 | } 149 | 150 | transformTriangle ( a, b, c ) = 151 | ( transform a, transform b, transform c ) 152 | in 153 | List.map transformTriangle square 154 | 155 | 156 | square : List ( Vertex, Vertex, Vertex ) 157 | square = 158 | let 159 | topLeft = 160 | Vertex (vec3 -1 1 1) (vec2 0 1) 161 | 162 | topRight = 163 | Vertex (vec3 1 1 1) (vec2 1 1) 164 | 165 | bottomLeft = 166 | Vertex (vec3 -1 -1 1) (vec2 0 0) 167 | 168 | bottomRight = 169 | Vertex (vec3 1 -1 1) (vec2 1 0) 170 | in 171 | [ ( topLeft, topRight, bottomLeft ) 172 | , ( bottomLeft, topRight, bottomRight ) 173 | ] 174 | 175 | 176 | 177 | -- VIEW 178 | 179 | 180 | view : Model -> Html Action 181 | view { textures, size, position } = 182 | case textures of 183 | Just ( faceTexture, sideTexture ) -> 184 | WebGL.toHtml 185 | [ width size.width 186 | , height size.height 187 | , style [ ( "display", "block" ) ] 188 | ] 189 | [ toEntity faceMesh faceTexture size position 190 | , toEntity sidesMesh sideTexture size position 191 | ] 192 | 193 | Nothing -> 194 | text "Loading textures..." 195 | 196 | 197 | toEntity : Mesh Vertex -> Texture -> Window.Size -> Mouse.Position -> Entity 198 | toEntity mesh texture { width, height } { x, y } = 199 | WebGL.entity 200 | vertexShader 201 | fragmentShader 202 | mesh 203 | { texture = texture 204 | , perspective = 205 | perspective 206 | (toFloat width) 207 | (toFloat height) 208 | (toFloat x) 209 | (toFloat y) 210 | } 211 | 212 | 213 | perspective : Float -> Float -> Float -> Float -> Mat4 214 | perspective width height x y = 215 | let 216 | eye = 217 | vec3 (0.5 - x / width) -(0.5 - y / height) 1 218 | |> Vec3.normalize 219 | |> Vec3.scale 6 220 | in 221 | Mat4.mul 222 | (Mat4.makePerspective 45 (width / height) 0.01 100) 223 | (Mat4.makeLookAt eye (vec3 0 0 0) Vec3.j) 224 | 225 | 226 | 227 | -- SHADERS 228 | 229 | 230 | type alias Uniforms = 231 | { perspective : Mat4 232 | , texture : Texture 233 | } 234 | 235 | 236 | vertexShader : Shader Vertex Uniforms { vcoord : Vec2 } 237 | vertexShader = 238 | [glsl| 239 | 240 | attribute vec3 position; 241 | attribute vec2 coord; 242 | uniform mat4 perspective; 243 | varying vec2 vcoord; 244 | 245 | void main () { 246 | gl_Position = perspective * vec4(position, 1.0); 247 | vcoord = coord.xy; 248 | } 249 | 250 | |] 251 | 252 | 253 | fragmentShader : Shader {} Uniforms { vcoord : Vec2 } 254 | fragmentShader = 255 | [glsl| 256 | 257 | precision mediump float; 258 | uniform sampler2D texture; 259 | varying vec2 vcoord; 260 | 261 | void main () { 262 | gl_FragColor = texture2D(texture, vcoord); 263 | } 264 | 265 | |] 266 | -------------------------------------------------------------------------------- /examples/crate.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (main) 2 | 3 | {- 4 | This example was inspired by https://open.gl/depthstencils 5 | It demonstrates how to use the stencil buffer. 6 | -} 7 | 8 | import AnimationFrame 9 | import Html exposing (Html) 10 | import Html.Attributes exposing (width, height, style) 11 | import Math.Matrix4 as Mat4 exposing (Mat4) 12 | import Math.Vector2 as Vec2 exposing (Vec2, vec2) 13 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 14 | import Task 15 | import Time exposing (Time) 16 | import WebGL exposing (Mesh, Shader, Entity) 17 | import WebGL.Settings.Blend as Blend 18 | import WebGL.Settings.DepthTest as DepthTest 19 | import WebGL.Settings.StencilTest as StencilTest 20 | import WebGL.Texture as Texture exposing (Error, Texture) 21 | 22 | 23 | type alias Model = 24 | { texture : Maybe Texture 25 | , theta : Float 26 | } 27 | 28 | 29 | type Msg 30 | = TextureLoaded (Result Error Texture) 31 | | Animate Time 32 | 33 | 34 | update : Msg -> Model -> ( Model, Cmd Msg ) 35 | update action model = 36 | case action of 37 | TextureLoaded textureResult -> 38 | ( { model | texture = Result.toMaybe textureResult }, Cmd.none ) 39 | 40 | Animate dt -> 41 | ( { model | theta = model.theta + dt / 10000 }, Cmd.none ) 42 | 43 | 44 | init : ( Model, Cmd Msg ) 45 | init = 46 | ( { texture = Nothing, theta = 0 } 47 | , Task.attempt TextureLoaded (Texture.load "texture/wood-crate.jpg") 48 | ) 49 | 50 | 51 | main : Program Never Model Msg 52 | main = 53 | Html.program 54 | { init = init 55 | , view = view 56 | , subscriptions = (\model -> AnimationFrame.diffs Animate) 57 | , update = update 58 | } 59 | 60 | 61 | 62 | -- View 63 | 64 | 65 | view : Model -> Html Msg 66 | view { texture, theta } = 67 | WebGL.toHtmlWith 68 | [ WebGL.alpha True 69 | , WebGL.antialias 70 | , WebGL.depth 1 71 | , WebGL.stencil 0 72 | ] 73 | [ width 400 74 | , height 400 75 | , style [ ( "display", "block" ) ] 76 | ] 77 | (texture 78 | |> Maybe.map (scene (perspective theta)) 79 | |> Maybe.withDefault [] 80 | ) 81 | 82 | 83 | perspective : Float -> Mat4 84 | perspective angle = 85 | List.foldr Mat4.mul 86 | Mat4.identity 87 | [ Mat4.makePerspective 45 1 0.01 100 88 | , Mat4.makeLookAt (vec3 0 3 8) (vec3 0 0 0) (vec3 0 1 0) 89 | , Mat4.makeRotate (3 * angle) (vec3 0 1 0) 90 | ] 91 | 92 | 93 | scene : Mat4 -> Texture -> List Entity 94 | scene camera texture = 95 | [ WebGL.entity 96 | crateVertex 97 | crateFragment 98 | crateMesh 99 | { texture = texture 100 | , perspective = camera 101 | } 102 | , WebGL.entityWith 103 | [ DepthTest.less 104 | { write = False 105 | , near = 0 106 | , far = 1 107 | } 108 | , StencilTest.test 109 | { ref = 1 110 | , mask = 0xFF 111 | , test = StencilTest.always 112 | , fail = StencilTest.keep 113 | , zfail = StencilTest.keep 114 | , zpass = StencilTest.replace 115 | , writeMask = 0xFF 116 | } 117 | ] 118 | floorVertex 119 | floorFragment 120 | floorMesh 121 | { texture = texture 122 | , perspective = camera 123 | } 124 | , WebGL.entityWith 125 | [ StencilTest.test 126 | { ref = 1 127 | , mask = 0xFF 128 | , test = StencilTest.equal 129 | , fail = StencilTest.keep 130 | , zfail = StencilTest.keep 131 | , zpass = StencilTest.keep 132 | , writeMask = 0 133 | } 134 | , DepthTest.default 135 | , Blend.custom 136 | { r = 0 137 | , g = 0 138 | , b = 0 139 | , a = 0.5 140 | , color = Blend.customAdd Blend.constantAlpha Blend.zero 141 | , alpha = Blend.customAdd Blend.one Blend.zero 142 | } 143 | ] 144 | crateVertex 145 | crateFragment 146 | crateMesh 147 | { texture = texture 148 | , perspective = 149 | Mat4.mul camera (Mat4.makeScale (vec3 1 -1 1)) 150 | } 151 | ] 152 | 153 | 154 | 155 | -- Meshes 156 | 157 | 158 | type alias Vertex = 159 | { position : Vec3 160 | , coord : Vec2 161 | } 162 | 163 | 164 | crateMesh : Mesh Vertex 165 | crateMesh = 166 | [ ( 0, 0 ), ( 90, 0 ), ( 180, 0 ), ( 270, 0 ), ( 0, 90 ), ( 0, 270 ) ] 167 | |> List.concatMap rotatedFace 168 | |> WebGL.triangles 169 | 170 | 171 | rotatedFace : ( Float, Float ) -> List ( Vertex, Vertex, Vertex ) 172 | rotatedFace ( angleXZ, angleYZ ) = 173 | let 174 | transformMat = 175 | List.foldr Mat4.mul 176 | Mat4.identity 177 | [ Mat4.makeTranslate (vec3 0 1 0) 178 | , Mat4.makeRotate (degrees angleXZ) Vec3.j 179 | , Mat4.makeRotate (degrees angleYZ) Vec3.i 180 | , Mat4.makeTranslate (vec3 0 0 1) 181 | ] 182 | 183 | transform vertex = 184 | { vertex 185 | | position = 186 | Mat4.transform 187 | transformMat 188 | vertex.position 189 | } 190 | 191 | transformTriangle ( a, b, c ) = 192 | ( transform a, transform b, transform c ) 193 | in 194 | List.map transformTriangle square 195 | 196 | 197 | square : List ( Vertex, Vertex, Vertex ) 198 | square = 199 | let 200 | topLeft = 201 | { position = vec3 -1 1 0, coord = vec2 0 1 } 202 | 203 | topRight = 204 | { position = vec3 1 1 0, coord = vec2 1 1 } 205 | 206 | bottomLeft = 207 | { position = vec3 -1 -1 0, coord = vec2 0 0 } 208 | 209 | bottomRight = 210 | { position = vec3 1 -1 0, coord = vec2 1 0 } 211 | in 212 | [ ( topLeft, topRight, bottomLeft ) 213 | , ( bottomLeft, topRight, bottomRight ) 214 | ] 215 | 216 | 217 | floorMesh : Mesh { position : Vec3 } 218 | floorMesh = 219 | let 220 | topLeft = 221 | { position = vec3 -2 0 -2 } 222 | 223 | topRight = 224 | { position = vec3 2 0 -2 } 225 | 226 | bottomLeft = 227 | { position = vec3 -2 0 2 } 228 | 229 | bottomRight = 230 | { position = vec3 2 0 2 } 231 | in 232 | WebGL.triangles 233 | [ ( topLeft, topRight, bottomLeft ) 234 | , ( bottomLeft, topRight, bottomRight ) 235 | ] 236 | 237 | 238 | 239 | -- Shaders 240 | 241 | 242 | type alias Uniforms = 243 | { perspective : Mat4 244 | , texture : Texture 245 | } 246 | 247 | 248 | crateVertex : Shader Vertex Uniforms { vcoord : Vec2 } 249 | crateVertex = 250 | [glsl| 251 | 252 | attribute vec3 position; 253 | attribute vec2 coord; 254 | uniform mat4 perspective; 255 | varying vec2 vcoord; 256 | 257 | void main () { 258 | gl_Position = perspective * vec4(position, 1.0); 259 | vcoord = coord; 260 | } 261 | 262 | |] 263 | 264 | 265 | crateFragment : Shader {} { u | texture : Texture } { vcoord : Vec2 } 266 | crateFragment = 267 | [glsl| 268 | 269 | precision mediump float; 270 | uniform sampler2D texture; 271 | varying vec2 vcoord; 272 | 273 | void main () { 274 | gl_FragColor = texture2D(texture, vcoord); 275 | } 276 | 277 | |] 278 | 279 | 280 | floorVertex : Shader { position : Vec3 } Uniforms {} 281 | floorVertex = 282 | [glsl| 283 | 284 | attribute vec3 position; 285 | uniform mat4 perspective; 286 | 287 | void main () { 288 | gl_Position = perspective * vec4(position, 1.0); 289 | } 290 | 291 | |] 292 | 293 | 294 | floorFragment : Shader attributes Uniforms {} 295 | floorFragment = 296 | [glsl| 297 | 298 | precision mediump float; 299 | 300 | void main () { 301 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 302 | } 303 | 304 | |] 305 | -------------------------------------------------------------------------------- /src/WebGL/Texture.elm: -------------------------------------------------------------------------------- 1 | module WebGL.Texture 2 | exposing 3 | ( Texture 4 | , Error(..) 5 | , load 6 | , loadWith 7 | , Options 8 | , defaultOptions 9 | , nonPowerOfTwoOptions 10 | , Resize 11 | , Bigger 12 | , Smaller 13 | , linear 14 | , nearest 15 | , nearestMipmapNearest 16 | , linearMipmapNearest 17 | , nearestMipmapLinear 18 | , linearMipmapLinear 19 | , Wrap 20 | , repeat 21 | , clampToEdge 22 | , mirroredRepeat 23 | , size 24 | ) 25 | 26 | {-| 27 | # Texture 28 | @docs Texture, load, Error, size 29 | 30 | # Custom Loading 31 | @docs loadWith, Options, defaultOptions 32 | 33 | ## Resizing 34 | @docs Resize, linear, nearest, 35 | nearestMipmapLinear, nearestMipmapNearest, 36 | linearMipmapNearest, linearMipmapLinear, 37 | Bigger, Smaller 38 | 39 | ## Wrapping 40 | @docs Wrap, repeat, clampToEdge, mirroredRepeat 41 | 42 | # Things You Shouldn’t Do 43 | @docs nonPowerOfTwoOptions 44 | -} 45 | 46 | import Task exposing (Task) 47 | import WebGL 48 | import Native.Texture 49 | 50 | 51 | {-| Textures can be passed in `uniforms`, and used in the fragment shader. 52 | You can create a texture with [`load`](#load) or [`loadWith`](#loadWith) 53 | and measure its dimensions with [`size`](#size). 54 | -} 55 | type alias Texture = 56 | WebGL.Texture 57 | 58 | 59 | {-| Loads a texture from the given url with default options. 60 | PNG and JPEG are known to work, but other formats have not been as 61 | well-tested yet. 62 | 63 | The Y axis of the texture is flipped automatically for you, so it has 64 | the same direction as in the clip-space, i.e. pointing up. 65 | 66 | If you need to change flipping, filtering or wrapping, you can use 67 | [`loadWith`](#loadWith). 68 | 69 | load url = 70 | loadWith defaultOptions url 71 | 72 | -} 73 | load : String -> Task Error Texture 74 | load = 75 | loadWith defaultOptions 76 | 77 | 78 | {-| Loading a texture can result in two kinds of errors: 79 | 80 | * `LoadError` means the image did not load for some reason. Maybe 81 | it was a network problem, or maybe it was a bad file format. 82 | 83 | * `SizeError` means you are trying to load a weird shaped image. 84 | For most operations you want a rectangle where the width is a power 85 | of two and the height is a power of two. This is more efficient on 86 | the GPU and it makes mipmapping possible. You can use 87 | [`nonPowerOfTwoOptions`](#nonPowerOfTwoOptions) to get things working 88 | now, but it is way better to create power-of-two assets! 89 | -} 90 | type Error 91 | = LoadError 92 | | SizeError Int Int 93 | 94 | 95 | {-| Same as load, but allows to set options. 96 | -} 97 | loadWith : Options -> String -> Task Error Texture 98 | loadWith { magnify, minify, horizontalWrap, verticalWrap, flipY } url = 99 | let 100 | expand (Resize mag) (Resize min) (Wrap hor) (Wrap vert) = 101 | Native.Texture.load mag min hor vert flipY url 102 | in 103 | expand magnify minify horizontalWrap verticalWrap 104 | 105 | 106 | {-| `Options` describe how to: 107 | 108 | * `magnify` - how to [`Resize`](#Resize) into a bigger texture 109 | * `minify` - how to [`Resize`](#Resize) into a smaller texture 110 | * `horizontalWrap` - how to [`Wrap`](#Wrap) the texture horizontally if the width is not a power of two 111 | * `verticalWrap` - how to [`Wrap`](#Wrap) the texture vertically if the height is not a power of two 112 | * `flipY` - flip the Y axis of the texture so it has the same direction 113 | as the clip-space, i.e. pointing up. 114 | 115 | You can read more about these parameters in the 116 | [specification](https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexParameter.xml). 117 | -} 118 | type alias Options = 119 | { magnify : Resize Bigger 120 | , minify : Resize Smaller 121 | , horizontalWrap : Wrap 122 | , verticalWrap : Wrap 123 | , flipY : Bool 124 | } 125 | 126 | 127 | {-| Default options for the loaded texture. 128 | 129 | { magnify = linear 130 | , minify = nearestMipmapLinear 131 | , horizontalWrap = repeat 132 | , verticalWrap = repeat 133 | , flipY = True 134 | } 135 | -} 136 | defaultOptions : Options 137 | defaultOptions = 138 | { magnify = linear 139 | , minify = nearestMipmapLinear 140 | , horizontalWrap = repeat 141 | , verticalWrap = repeat 142 | , flipY = True 143 | } 144 | 145 | 146 | {-| The exact options needed to load textures with weird shapes. 147 | If your image width or height is not a power of two, you need these 148 | options: 149 | 150 | { magnify = linear 151 | , minify = nearest 152 | , horizontalWrap = clampToEdge 153 | , verticalWrap = clampToEdge 154 | , flipY = True 155 | } 156 | -} 157 | nonPowerOfTwoOptions : Options 158 | nonPowerOfTwoOptions = 159 | { magnify = linear 160 | , minify = nearest 161 | , horizontalWrap = clampToEdge 162 | , verticalWrap = clampToEdge 163 | , flipY = True 164 | } 165 | 166 | 167 | 168 | -- RESIZING 169 | 170 | 171 | {-| How to resize a texture. 172 | -} 173 | type Resize a 174 | = Resize Int 175 | 176 | 177 | {-| Returns the weighted average of the four texture elements that are closest 178 | to the center of the pixel being textured. 179 | -} 180 | linear : Resize a 181 | linear = 182 | Resize 9729 183 | 184 | 185 | {-| Returns the value of the texture element that is nearest 186 | (in Manhattan distance) to the center of the pixel being textured. 187 | -} 188 | nearest : Resize a 189 | nearest = 190 | Resize 9728 191 | 192 | 193 | {-| Chooses the mipmap that most closely matches the size of the pixel being 194 | textured and uses the `nearest` criterion (the texture element nearest to 195 | the center of the pixel) to produce a texture value. 196 | 197 | A mipmap is an ordered set of arrays representing the same image at 198 | progressively lower resolutions. 199 | 200 | This is the default value of the minify filter. 201 | -} 202 | nearestMipmapNearest : Resize Smaller 203 | nearestMipmapNearest = 204 | Resize 9984 205 | 206 | 207 | {-| Chooses the mipmap that most closely matches the size of the pixel being 208 | textured and uses the `linear` criterion (a weighted average of the four 209 | texture elements that are closest to the center of the pixel) to produce a 210 | texture value. 211 | -} 212 | linearMipmapNearest : Resize Smaller 213 | linearMipmapNearest = 214 | Resize 9985 215 | 216 | 217 | {-| Chooses the two mipmaps that most closely match the size of the pixel being 218 | textured and uses the `nearest` criterion (the texture element nearest to the 219 | center of the pixel) to produce a texture value from each mipmap. The final 220 | texture value is a weighted average of those two values. 221 | -} 222 | nearestMipmapLinear : Resize Smaller 223 | nearestMipmapLinear = 224 | Resize 9986 225 | 226 | 227 | {-| Chooses the two mipmaps that most closely match the size of the pixel being 228 | textured and uses the `linear` criterion (a weighted average of the four 229 | texture elements that are closest to the center of the pixel) to produce a 230 | texture value from each mipmap. The final texture value is a weighted average 231 | of those two values. 232 | -} 233 | linearMipmapLinear : Resize Smaller 234 | linearMipmapLinear = 235 | Resize 9987 236 | 237 | 238 | {-| Helps restrict `options.magnify` to only allow 239 | [`linear`](#linear) and [`nearest`](#nearest). 240 | -} 241 | type Bigger 242 | = Bigger 243 | 244 | 245 | {-| Helps restrict `options.magnify`, while also allowing 246 | `options.minify` to use mipmapping resizes, like 247 | [`nearestMipmapNearest`](#nearestMipmapNearest). 248 | -} 249 | type Smaller 250 | = Smaller 251 | 252 | 253 | {-| Sets the wrap parameter for texture coordinate. 254 | -} 255 | type Wrap 256 | = Wrap Int 257 | 258 | 259 | {-| Causes the integer part of the coordinate to be ignored. This is the 260 | default value for both texture axis. 261 | -} 262 | repeat : Wrap 263 | repeat = 264 | Wrap 10497 265 | 266 | 267 | {-| Causes coordinates to be clamped to the range 1 2N 1 - 1 2N, where N is 268 | the size of the texture in the direction of clamping. 269 | -} 270 | clampToEdge : Wrap 271 | clampToEdge = 272 | Wrap 33071 273 | 274 | 275 | {-| Causes the coordinate c to be set to the fractional part of the texture 276 | coordinate if the integer part is even; if the integer part is odd, then 277 | the coordinate is set to 1 - frac, where frac represents the fractional part 278 | of the coordinate. 279 | -} 280 | mirroredRepeat : Wrap 281 | mirroredRepeat = 282 | Wrap 33648 283 | 284 | 285 | {-| Return the (width, height) size of a texture. Useful for sprite sheets 286 | or other times you may want to use only a potion of a texture image. 287 | -} 288 | size : Texture -> ( Int, Int ) 289 | size = 290 | Native.Texture.size 291 | -------------------------------------------------------------------------------- /examples/first-person.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (main) 2 | 3 | {- 4 | Try adding the ability to crouch or to land on top of the crate. 5 | -} 6 | 7 | import AnimationFrame 8 | import Html exposing (Html, text, div) 9 | import Html.Attributes exposing (width, height, style) 10 | import Keyboard 11 | import Math.Matrix4 as Mat4 exposing (Mat4) 12 | import Math.Vector2 as Vec2 exposing (Vec2, vec2) 13 | import Math.Vector3 as Vec3 exposing (Vec3, vec3) 14 | import Task exposing (Task) 15 | import Time exposing (Time) 16 | import WebGL exposing (Mesh, Shader, Entity) 17 | import WebGL.Texture as Texture exposing (Texture, Error) 18 | import Window 19 | 20 | 21 | type alias Model = 22 | { texture : Maybe Texture 23 | , keys : Keys 24 | , size : Window.Size 25 | , person : Person 26 | } 27 | 28 | 29 | type alias Person = 30 | { position : Vec3 31 | , velocity : Vec3 32 | } 33 | 34 | 35 | type Msg 36 | = TextureLoaded (Result Error Texture) 37 | | KeyChange Bool Keyboard.KeyCode 38 | | Animate Time 39 | | Resize Window.Size 40 | 41 | 42 | type alias Keys = 43 | { left : Bool 44 | , right : Bool 45 | , up : Bool 46 | , down : Bool 47 | , space : Bool 48 | } 49 | 50 | 51 | main : Program Never Model Msg 52 | main = 53 | Html.program 54 | { init = init 55 | , view = view 56 | , subscriptions = subscriptions 57 | , update = update 58 | } 59 | 60 | 61 | eyeLevel : Float 62 | eyeLevel = 63 | 2 64 | 65 | 66 | init : ( Model, Cmd Msg ) 67 | init = 68 | ( { texture = Nothing 69 | , person = Person (vec3 0 eyeLevel -10) (vec3 0 0 0) 70 | , keys = Keys False False False False False 71 | , size = Window.Size 0 0 72 | } 73 | , Cmd.batch 74 | [ Task.attempt TextureLoaded (Texture.load "texture/wood-crate.jpg") 75 | , Task.perform Resize Window.size 76 | ] 77 | ) 78 | 79 | 80 | subscriptions : Model -> Sub Msg 81 | subscriptions _ = 82 | Sub.batch 83 | [ AnimationFrame.diffs Animate 84 | , Keyboard.downs (KeyChange True) 85 | , Keyboard.ups (KeyChange False) 86 | , Window.resizes Resize 87 | ] 88 | 89 | 90 | update : Msg -> Model -> ( Model, Cmd Msg ) 91 | update action model = 92 | case action of 93 | TextureLoaded textureResult -> 94 | ( { model | texture = Result.toMaybe textureResult }, Cmd.none ) 95 | 96 | KeyChange on code -> 97 | ( { model | keys = keyFunc on code model.keys }, Cmd.none ) 98 | 99 | Resize size -> 100 | ( { model | size = size }, Cmd.none ) 101 | 102 | Animate dt -> 103 | ( { model 104 | | person = 105 | model.person 106 | |> move model.keys 107 | |> gravity (dt / 500) 108 | |> physics (dt / 500) 109 | } 110 | , Cmd.none 111 | ) 112 | 113 | 114 | keyFunc : Bool -> Keyboard.KeyCode -> Keys -> Keys 115 | keyFunc on keyCode keys = 116 | case keyCode of 117 | 32 -> 118 | { keys | space = on } 119 | 120 | 37 -> 121 | { keys | left = on } 122 | 123 | 39 -> 124 | { keys | right = on } 125 | 126 | 38 -> 127 | { keys | up = on } 128 | 129 | 40 -> 130 | { keys | down = on } 131 | 132 | _ -> 133 | keys 134 | 135 | 136 | move : Keys -> Person -> Person 137 | move { left, right, up, down, space } person = 138 | let 139 | direction a b = 140 | if a == b then 141 | 0 142 | else if a then 143 | 1 144 | else 145 | -1 146 | 147 | vy = 148 | if space then 149 | 2 150 | else 151 | Vec3.getY person.velocity 152 | in 153 | if Vec3.getY person.position <= eyeLevel then 154 | { person 155 | | velocity = 156 | vec3 (direction left right) vy (direction up down) 157 | } 158 | else 159 | person 160 | 161 | 162 | physics : Float -> Person -> Person 163 | physics dt person = 164 | let 165 | position = 166 | Vec3.add person.position (Vec3.scale dt person.velocity) 167 | in 168 | { person 169 | | position = 170 | if Vec3.getY position < eyeLevel then 171 | Vec3.setY eyeLevel position 172 | else 173 | position 174 | } 175 | 176 | 177 | gravity : Float -> Person -> Person 178 | gravity dt person = 179 | if Vec3.getY person.position > eyeLevel then 180 | { person 181 | | velocity = 182 | Vec3.setY 183 | (Vec3.getY person.velocity - 2 * dt) 184 | person.velocity 185 | } 186 | else 187 | person 188 | 189 | 190 | 191 | -- View 192 | 193 | 194 | view : Model -> Html Msg 195 | view { size, person, texture } = 196 | div 197 | [ style 198 | [ ( "width", toString size.width ++ "px" ) 199 | , ( "height", toString size.height ++ "px" ) 200 | , ( "position", "relative" ) 201 | ] 202 | ] 203 | [ WebGL.toHtmlWith 204 | [ WebGL.depth 1 205 | ] 206 | [ width size.width 207 | , height size.height 208 | , style [ ( "display", "block" ) ] 209 | ] 210 | (texture 211 | |> Maybe.map (scene size person) 212 | |> Maybe.withDefault [] 213 | ) 214 | , div 215 | [ style 216 | [ ( "position", "absolute" ) 217 | , ( "font-family", "monospace" ) 218 | , ( "color", "white" ) 219 | , ( "text-align", "center" ) 220 | , ( "left", "20px" ) 221 | , ( "right", "20px" ) 222 | , ( "top", "20px" ) 223 | ] 224 | ] 225 | [ text message ] 226 | ] 227 | 228 | 229 | message : String 230 | message = 231 | "Walk around with a first person perspective.\n" 232 | ++ "Arrows keys to move, space bar to jump." 233 | 234 | 235 | scene : Window.Size -> Person -> Texture -> List Entity 236 | scene { width, height } person texture = 237 | let 238 | perspective = 239 | Mat4.mul 240 | (Mat4.makePerspective 45 (toFloat width / toFloat height) 0.01 100) 241 | (Mat4.makeLookAt person.position (Vec3.add person.position Vec3.k) Vec3.j) 242 | in 243 | [ WebGL.entity 244 | vertexShader 245 | fragmentShader 246 | crate 247 | { texture = texture 248 | , perspective = perspective 249 | } 250 | ] 251 | 252 | 253 | 254 | -- Mesh 255 | 256 | 257 | type alias Vertex = 258 | { position : Vec3 259 | , coord : Vec2 260 | } 261 | 262 | 263 | crate : Mesh Vertex 264 | crate = 265 | [ ( 0, 0 ), ( 90, 0 ), ( 180, 0 ), ( 270, 0 ), ( 0, 90 ), ( 0, -90 ) ] 266 | |> List.concatMap rotatedSquare 267 | |> WebGL.triangles 268 | 269 | 270 | rotatedSquare : ( Float, Float ) -> List ( Vertex, Vertex, Vertex ) 271 | rotatedSquare ( angleXZ, angleYZ ) = 272 | let 273 | transformMat = 274 | Mat4.mul 275 | (Mat4.makeRotate (degrees angleXZ) Vec3.j) 276 | (Mat4.makeRotate (degrees angleYZ) Vec3.i) 277 | 278 | transform vertex = 279 | { vertex 280 | | position = 281 | Mat4.transform transformMat vertex.position 282 | } 283 | 284 | transformTriangle ( a, b, c ) = 285 | ( transform a, transform b, transform c ) 286 | in 287 | List.map transformTriangle square 288 | 289 | 290 | square : List ( Vertex, Vertex, Vertex ) 291 | square = 292 | let 293 | topLeft = 294 | Vertex (vec3 -1 1 1) (vec2 0 1) 295 | 296 | topRight = 297 | Vertex (vec3 1 1 1) (vec2 1 1) 298 | 299 | bottomLeft = 300 | Vertex (vec3 -1 -1 1) (vec2 0 0) 301 | 302 | bottomRight = 303 | Vertex (vec3 1 -1 1) (vec2 1 0) 304 | in 305 | [ ( topLeft, topRight, bottomLeft ) 306 | , ( bottomLeft, topRight, bottomRight ) 307 | ] 308 | 309 | 310 | 311 | -- Shaders 312 | 313 | 314 | type alias Uniforms = 315 | { texture : Texture 316 | , perspective : Mat4 317 | } 318 | 319 | 320 | vertexShader : Shader Vertex Uniforms { vcoord : Vec2 } 321 | vertexShader = 322 | [glsl| 323 | 324 | attribute vec3 position; 325 | attribute vec2 coord; 326 | uniform mat4 perspective; 327 | varying vec2 vcoord; 328 | 329 | void main () { 330 | gl_Position = perspective * vec4(position, 1.0); 331 | vcoord = coord; 332 | } 333 | 334 | |] 335 | 336 | 337 | fragmentShader : Shader {} Uniforms { vcoord : Vec2 } 338 | fragmentShader = 339 | [glsl| 340 | 341 | precision mediump float; 342 | uniform sampler2D texture; 343 | varying vec2 vcoord; 344 | 345 | void main () { 346 | gl_FragColor = texture2D(texture, vcoord); 347 | } 348 | 349 | |] 350 | -------------------------------------------------------------------------------- /src/WebGL.elm: -------------------------------------------------------------------------------- 1 | module WebGL 2 | exposing 3 | ( Texture 4 | , Shader 5 | , Entity 6 | , Mesh 7 | , triangles 8 | , indexedTriangles 9 | , lines 10 | , lineStrip 11 | , lineLoop 12 | , points 13 | , triangleFan 14 | , triangleStrip 15 | , entity 16 | , entityWith 17 | , toHtml 18 | , toHtmlWith 19 | , Option 20 | , alpha 21 | , depth 22 | , stencil 23 | , antialias 24 | , clearColor 25 | , unsafeShader 26 | ) 27 | 28 | {-| The WebGL API is for high performance rendering. Definitely read about 29 | [how WebGL works](http://package.elm-lang.org/packages/elm-community/webgl/latest) 30 | and look at [some examples](https://github.com/elm-community/webgl/tree/master/examples) 31 | before trying to do too much with just the documentation provided here. 32 | 33 | # Mesh 34 | @docs Mesh, triangles 35 | 36 | # Shaders 37 | @docs Shader, Texture 38 | 39 | # Entities 40 | @docs Entity, entity 41 | 42 | # WebGL Html 43 | @docs toHtml 44 | 45 | # Advanced Usage 46 | @docs entityWith, toHtmlWith, Option, alpha, depth, stencil, antialias, 47 | clearColor 48 | 49 | # Meshes 50 | @docs indexedTriangles, lines, lineStrip, lineLoop, points, triangleFan, 51 | triangleStrip 52 | 53 | # Unsafe Shader Creation (for library writers) 54 | @docs unsafeShader 55 | -} 56 | 57 | import Html exposing (Html, Attribute) 58 | import WebGL.Settings as Settings exposing (Setting) 59 | import WebGL.Settings.DepthTest as DepthTest 60 | import Native.WebGL 61 | 62 | 63 | {-| Mesh forms geometry from the specified vertices. Each vertex contains a 64 | bunch of attributes, defined as a custom record type, e.g.: 65 | 66 | type alias Attributes = 67 | { position : Vec3 68 | , color : Vec3 69 | } 70 | 71 | The supported types in attributes are: `Int`, `Float`, `WebGL.Texture` 72 | and `Vec2`, `Vec3`, `Vec4`, `Mat4` from the 73 | [linear-algebra](http://package.elm-lang.org/packages/elm-community/linear-algebra/latest) 74 | package. 75 | 76 | Do not generate meshes in `view`, [read more about this here](http://package.elm-lang.org/packages/elm-community/webgl/latest#making-the-most-of-the-gpu). 77 | -} 78 | type Mesh attributes 79 | = Triangles (List ( attributes, attributes, attributes )) 80 | | Lines (List ( attributes, attributes )) 81 | | LineStrip (List attributes) 82 | | LineLoop (List attributes) 83 | | Points (List attributes) 84 | | TriangleFan (List attributes) 85 | | TriangleStrip (List attributes) 86 | | IndexedTriangles (List attributes) (List ( Int, Int, Int )) 87 | 88 | 89 | {-| Triangles are the basic building blocks of a mesh. You can put them together 90 | to form any shape. 91 | 92 | So when you create `triangles` you are really providing three sets of attributes 93 | that describe the corners of each triangle. 94 | -} 95 | triangles : List ( attributes, attributes, attributes ) -> Mesh attributes 96 | triangles = 97 | Triangles 98 | 99 | 100 | {-| Creates a strip of triangles where each additional vertex creates an 101 | additional triangle once the first three vertices have been drawn. 102 | -} 103 | triangleStrip : List attributes -> Mesh attributes 104 | triangleStrip = 105 | TriangleStrip 106 | 107 | 108 | {-| Similar to [`triangleStrip`](#triangleStrip), but creates a fan shaped 109 | output. 110 | -} 111 | triangleFan : List attributes -> Mesh attributes 112 | triangleFan = 113 | TriangleFan 114 | 115 | 116 | {-| Create triangles from vertices and indices, grouped in sets of three to 117 | define each triangle by refering the vertices. 118 | 119 | This helps to avoid duplicated vertices whenever two triangles share an 120 | edge. For example, if you want to define a rectangle using 121 | [`triangles`](#triangles), `v0` and `v2` will have to be duplicated: 122 | 123 | -- v2 +---+ v1 124 | -- |\ | 125 | -- | \ | 126 | -- | \| 127 | -- v3 +---+ v0 128 | 129 | rectangle = 130 | triangles [(v0, v1, v2), (v2, v3, v0)] 131 | 132 | This will use two vertices less: 133 | 134 | rectangle = 135 | indexedTriangles [v0, v1, v2, v3] [(0, 1, 2), (2, 3, 0)] 136 | -} 137 | indexedTriangles : List attributes -> List ( Int, Int, Int ) -> Mesh attributes 138 | indexedTriangles = 139 | IndexedTriangles 140 | 141 | 142 | {-| Connects each pair of vertices with a line. 143 | -} 144 | lines : List ( attributes, attributes ) -> Mesh attributes 145 | lines = 146 | Lines 147 | 148 | 149 | {-| Connects each two subsequent vertices with a line. 150 | -} 151 | lineStrip : List attributes -> Mesh attributes 152 | lineStrip = 153 | LineStrip 154 | 155 | 156 | {-| Similar to [`lineStrip`](#lineStrip), but connects the last vertex back to 157 | the first. 158 | -} 159 | lineLoop : List attributes -> Mesh attributes 160 | lineLoop = 161 | LineLoop 162 | 163 | 164 | {-| Draws a single dot per vertex. 165 | -} 166 | points : List attributes -> Mesh attributes 167 | points = 168 | Points 169 | 170 | 171 | {-| Shaders are programs for running many computations on the GPU in parallel. 172 | They are written in a language called 173 | [GLSL](http://en.wikipedia.org/wiki/OpenGL_Shading_Language). Read more about 174 | shaders [here](https://github.com/elm-community/webgl/blob/master/README.md). 175 | 176 | Normally you specify a shader with a `[glsl| |]` block. Elm compiler will parse 177 | the shader code block and derive the type signature for your shader. 178 | 179 | * `attributes` define vertices in the [mesh](#Mesh); 180 | * `uniforms` allow you to pass scene parameters like 181 | transformation matrix, texture, screen size, etc.; 182 | * `varyings` define the output from the vertex shader. 183 | 184 | `attributes`, `uniforms` and `varyings` are records with the fields of the 185 | following types: `Int`, `Float`, [`Texture`](#Texture) and `Vec2`, `Vec3`, `Vec4`, 186 | `Mat4` from the 187 | [linear-algebra](http://package.elm-lang.org/packages/elm-community/linear-algebra/latest) 188 | package. 189 | -} 190 | type Shader attributes uniforms varyings 191 | = Shader 192 | 193 | 194 | {-| Creates a shader with a raw string of GLSL. It is intended specifically 195 | for library writers, who want to create shader combinators. 196 | -} 197 | unsafeShader : String -> Shader attributes uniforms varyings 198 | unsafeShader = 199 | Native.WebGL.unsafeCoerceGLSL 200 | 201 | 202 | {-| Use `Texture` to pass the `sampler2D` uniform value to the shader. Find 203 | more about textures in [`WebGL.Texture`](WebGL-Texture). 204 | -} 205 | type Texture 206 | = Texture 207 | 208 | 209 | {-| Conceptually, an encapsulation of the instructions to render something. 210 | -} 211 | type Entity 212 | = Entity 213 | 214 | 215 | {-| Packages a vertex shader, a fragment shader, a mesh, and uniforms 216 | as an `Entity`. This specifies a full rendering pipeline to be run 217 | on the GPU. You can read more about the pipeline 218 | [here](https://github.com/elm-community/webgl/blob/master/README.md). 219 | 220 | The vertex shader receives `attributes` and `uniforms` and returns `varyings` 221 | and `gl_Position`—the position of the vertex on the screen, defined as 222 | `vec4(x, y, z, w)`, that means `(x/w, y/w, z/w)` in the clip space coordinates: 223 | 224 | -- (-1,1,1) +================+ (1,1,1) 225 | -- /| /| 226 | -- / | | / | 227 | --(-1,1,-1)+================+ (1,1,-1) 228 | -- | | | / | | 229 | -- | | |/ | | 230 | -- | | +-------|->| 231 | -- (-1,-1,1|) +--(0,0,0)----|--+ (1,-1,1) 232 | -- | / | / 233 | -- |/ |/ 234 | -- +================+ 235 | -- (-1,-1,-1) (1,-1,-1) 236 | 237 | The fragment shader is called for each pixel inside the clip space with 238 | `varyings` and `uniforms` and returns `gl_FragColor`—the color of 239 | the pixel, defined as `vec4(r, g, b, a)` where each color component is a float 240 | from 0 to 1. 241 | 242 | Shaders and a mesh are cached so that they do not get resent to the GPU. 243 | It should be relatively cheap to create new entities out of existing 244 | values. 245 | 246 | By default, [depth test](WebGL-Settings-DepthTest#default) is enabled for you. 247 | If you need more [settings](WebGL-Settings), like 248 | [blending](WebGL-Settings-Blend) or [stencil test](WebG-Settings-StencilTest), 249 | then use [`entityWith`](#entityWith). 250 | 251 | entity = 252 | entityWith [ DepthTest.default ] 253 | -} 254 | entity : 255 | Shader attributes uniforms varyings 256 | -> Shader {} uniforms varyings 257 | -> Mesh attributes 258 | -> uniforms 259 | -> Entity 260 | entity = 261 | entityWith [ DepthTest.default ] 262 | 263 | 264 | {-| The same as [`entity`](#entity), but allows to configure an entity with 265 | [settings](WebGL-Settings). 266 | -} 267 | entityWith : 268 | List Setting 269 | -> Shader attributes uniforms varyings 270 | -> Shader {} uniforms varyings 271 | -> Mesh attributes 272 | -> uniforms 273 | -> Entity 274 | entityWith = 275 | Native.WebGL.entity 276 | 277 | 278 | {-| Render a WebGL scene with the given html attributes, and entities. 279 | 280 | `width` and `height` html attributes resize the drawing buffer, while 281 | the corresponding css properties scale the canvas element. 282 | 283 | To prevent blurriness on retina screens, you may want the drawing buffer 284 | to be twice the size of the canvas element. 285 | 286 | To remove an extra whitespace around the canvas, set `display: block`. 287 | 288 | By default, alpha channel with premultiplied alpha, antialias and depth buffer 289 | are enabled. Use [`toHtmlWith`](#toHtmlWith) for custom options. 290 | 291 | toHtml = 292 | toHtmlWith [ alpha True, antialias, depth 1 ] 293 | -} 294 | toHtml : List (Attribute msg) -> List Entity -> Html msg 295 | toHtml = 296 | toHtmlWith [ alpha True, antialias, depth 1 ] 297 | 298 | 299 | {-| Render a WebGL scene with the given options, html attributes, and entities. 300 | 301 | Due to browser limitations, options will be applied only once, 302 | when the canvas is created for the first time. 303 | -} 304 | toHtmlWith : List Option -> List (Attribute msg) -> List Entity -> Html msg 305 | toHtmlWith options attributes entities = 306 | Native.WebGL.toHtml options attributes entities 307 | 308 | 309 | {-| Provides a way to enable features and change the scene behavior 310 | in [`toHtmlWith`](#toHtmlWith). 311 | -} 312 | type Option 313 | = Alpha Bool 314 | | Depth Float 315 | | Stencil Int 316 | | Antialias 317 | | ClearColor Float Float Float Float 318 | 319 | 320 | {-| Enable alpha channel in the drawing buffer. If the argument is `True`, then 321 | the page compositor will assume the drawing buffer contains colors with 322 | premultiplied alpha `(r * a, g * a, b * a, a)`. 323 | -} 324 | alpha : Bool -> Option 325 | alpha = 326 | Alpha 327 | 328 | 329 | {-| Enable the depth buffer, and prefill it with given value each time before 330 | the scene is rendered. The value is clamped between 0 and 1. 331 | -} 332 | depth : Float -> Option 333 | depth = 334 | Depth 335 | 336 | 337 | {-| Enable the stencil buffer, specifying the index used to fill the 338 | stencil buffer before we render the scene. The index is masked with 2^m - 1, 339 | where m >= 8 is the number of bits in the stencil buffer. The default is 0. 340 | -} 341 | stencil : Int -> Option 342 | stencil = 343 | Stencil 344 | 345 | 346 | {-| Enable multisample antialiasing of the drawing buffer, if supported by 347 | the platform. Useful when you need to have smooth lines and smooth edges of 348 | triangles at a lower cost than supersampling (rendering to larger dimensions and 349 | then scaling down with CSS transform). 350 | -} 351 | antialias : Option 352 | antialias = 353 | Antialias 354 | 355 | 356 | {-| Set the red, green, blue and alpha channels, that will be used to 357 | fill the drawing buffer every time before drawing the scene. The values are 358 | clamped between 0 and 1. The default is all 0's. 359 | -} 360 | clearColor : Float -> Float -> Float -> Float -> Option 361 | clearColor = 362 | ClearColor 363 | -------------------------------------------------------------------------------- /src/Native/WebGL.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars, camelcase 2 | var _elm_community$webgl$Native_WebGL = function () { 3 | 4 | // setup logging 5 | // eslint-disable-next-line no-unused-vars 6 | function LOG(msg) { 7 | // console.log(msg); 8 | } 9 | 10 | function guid() { 11 | // eslint-disable-next-line camelcase 12 | return _elm_lang$core$Native_Utils.guid(); 13 | } 14 | function listEach(fn, list) { 15 | while (list.ctor !== '[]') { 16 | fn(list._0); 17 | list = list._1; 18 | } 19 | } 20 | function listLength(list) { 21 | var length = 0; 22 | while (list.ctor !== '[]') { 23 | length++; 24 | list = list._1; 25 | } 26 | return length; 27 | } 28 | 29 | var rAF = typeof requestAnimationFrame !== 'undefined' ? 30 | requestAnimationFrame : 31 | function (cb) { setTimeout(cb, 1000 / 60); }; 32 | 33 | function unsafeCoerceGLSL(src) { 34 | return { src: src }; 35 | } 36 | 37 | function entity(settings, vert, frag, buffer, uniforms) { 38 | 39 | if (!buffer.guid) { 40 | buffer.guid = guid(); 41 | } 42 | 43 | return { 44 | ctor: 'Entity', 45 | vert: vert, 46 | frag: frag, 47 | buffer: buffer, 48 | uniforms: uniforms, 49 | settings: settings 50 | }; 51 | 52 | } 53 | 54 | /** 55 | * Apply setting to the gl context 56 | * 57 | * @param {WebGLRenderingContext} gl context 58 | * @param {Setting} setting coming in from Elm 59 | */ 60 | function applySetting(gl, setting) { 61 | switch (setting.ctor) { 62 | case 'Blend': 63 | gl.enable(gl.BLEND); 64 | // eq1 f11 f12 eq2 f21 f22 r g b a 65 | gl.blendEquationSeparate(setting._0, setting._3); 66 | gl.blendFuncSeparate(setting._1, setting._2, setting._4, setting._5); 67 | gl.blendColor(setting._6, setting._7, setting._8, setting._9); 68 | break; 69 | case 'DepthTest': 70 | gl.enable(gl.DEPTH_TEST); 71 | // func mask near far 72 | gl.depthFunc(setting._0); 73 | gl.depthMask(setting._1); 74 | gl.depthRange(setting._2, setting._3); 75 | break; 76 | case 'StencilTest': 77 | gl.enable(gl.STENCIL_TEST); 78 | // ref mask writeMask test1 fail1 zfail1 zpass1 test2 fail2 zfail2 zpass2 79 | gl.stencilFuncSeparate(gl.FRONT, setting._3, setting._0, setting._1); 80 | gl.stencilOpSeparate(gl.FRONT, setting._4, setting._5, setting._6); 81 | gl.stencilMaskSeparate(gl.FRONT, setting._2); 82 | gl.stencilFuncSeparate(gl.BACK, setting._7, setting._0, setting._1); 83 | gl.stencilOpSeparate(gl.BACK, setting._8, setting._9, setting._10); 84 | gl.stencilMaskSeparate(gl.BACK, setting._2); 85 | break; 86 | case 'Scissor': 87 | gl.enable(gl.SCISSOR_TEST); 88 | gl.scissor(setting._0, setting._1, setting._2, setting._3); 89 | break; 90 | case 'ColorMask': 91 | gl.colorMask(setting._0, setting._1, setting._2, setting._3); 92 | break; 93 | case 'CullFace': 94 | gl.enable(gl.CULL_FACE); 95 | gl.cullFace(setting._0); 96 | break; 97 | case 'PolygonOffset': 98 | gl.enable(gl.POLYGON_OFFSET_FILL); 99 | gl.polygonOffset(setting._0, setting._1); 100 | break; 101 | case 'SampleCoverage': 102 | gl.enable(gl.SAMPLE_COVERAGE); 103 | gl.sampleCoverage(setting._0, setting._1); 104 | break; 105 | case 'SampleAlphaToCoverage': 106 | gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE); 107 | break; 108 | } 109 | } 110 | 111 | /** 112 | * Revert setting that was applied to the gl context 113 | * 114 | * @param {WebGLRenderingContext} gl context 115 | * @param {Setting} setting coming in from Elm 116 | */ 117 | function revertSetting(gl, setting) { 118 | switch (setting.ctor) { 119 | case 'Blend': 120 | gl.disable(gl.BLEND); 121 | break; 122 | case 'DepthTest': 123 | gl.disable(gl.DEPTH_TEST); 124 | break; 125 | case 'StencilTest': 126 | gl.disable(gl.STENCIL_TEST); 127 | break; 128 | case 'Scissor': 129 | gl.disable(gl.SCISSOR_TEST); 130 | break; 131 | case 'ColorMask': 132 | gl.colorMask(true, true, true, true); 133 | break; 134 | case 'CullFace': 135 | gl.disable(gl.CULL_FACE); 136 | break; 137 | case 'PolygonOffset': 138 | gl.disable(gl.POLYGON_OFFSET_FILL); 139 | break; 140 | case 'SampleCoverage': 141 | gl.disable(gl.SAMPLE_COVERAGE); 142 | break; 143 | case 'SampleAlphaToCoverage': 144 | gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE); 145 | break; 146 | } 147 | } 148 | 149 | function doCompile(gl, src, type) { 150 | 151 | var shader = gl.createShader(type); 152 | LOG('Created shader'); 153 | 154 | gl.shaderSource(shader, src); 155 | gl.compileShader(shader); 156 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 157 | throw gl.getShaderInfoLog(shader); 158 | } 159 | 160 | return shader; 161 | 162 | } 163 | 164 | function doLink(gl, vshader, fshader) { 165 | 166 | var program = gl.createProgram(); 167 | LOG('Created program'); 168 | 169 | gl.attachShader(program, vshader); 170 | gl.attachShader(program, fshader); 171 | gl.linkProgram(program); 172 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 173 | throw gl.getProgramInfoLog(program); 174 | } 175 | 176 | return program; 177 | 178 | } 179 | 180 | function getRenderInfo(gl, renderType) { 181 | switch (renderType) { 182 | case 'Triangles': 183 | return { mode: gl.TRIANGLES, elemSize: 3, indexSize: 0 }; 184 | case 'LineStrip': 185 | return { mode: gl.LINE_STRIP, elemSize: 1, indexSize: 0 }; 186 | case 'LineLoop': 187 | return { mode: gl.LINE_LOOP, elemSize: 1, indexSize: 0 }; 188 | case 'Points': 189 | return { mode: gl.POINTS, elemSize: 1, indexSize: 0 }; 190 | case 'Lines': 191 | return { mode: gl.LINES, elemSize: 2, indexSize: 0 }; 192 | case 'TriangleStrip': 193 | return { mode: gl.TRIANGLE_STRIP, elemSize: 1, indexSize: 0 }; 194 | case 'TriangleFan': 195 | return { mode: gl.TRIANGLE_FAN, elemSize: 1, indexSize: 0 }; 196 | case 'IndexedTriangles': 197 | return { mode: gl.TRIANGLES, elemSize: 1, indexSize: 3 }; 198 | } 199 | } 200 | 201 | function getAttributeInfo(gl, type) { 202 | switch (type) { 203 | case gl.FLOAT: 204 | return { size: 1, type: Float32Array, baseType: gl.FLOAT }; 205 | case gl.FLOAT_VEC2: 206 | return { size: 2, type: Float32Array, baseType: gl.FLOAT }; 207 | case gl.FLOAT_VEC3: 208 | return { size: 3, type: Float32Array, baseType: gl.FLOAT }; 209 | case gl.FLOAT_VEC4: 210 | return { size: 4, type: Float32Array, baseType: gl.FLOAT }; 211 | case gl.INT: 212 | return { size: 1, type: Int32Array, baseType: gl.INT }; 213 | case gl.INT_VEC2: 214 | return { size: 2, type: Int32Array, baseType: gl.INT }; 215 | case gl.INT_VEC3: 216 | return { size: 3, type: Int32Array, baseType: gl.INT }; 217 | case gl.INT_VEC4: 218 | return { size: 4, type: Int32Array, baseType: gl.INT }; 219 | } 220 | } 221 | 222 | /** 223 | * Form the buffer for a given attribute. 224 | * 225 | * @param {WebGLRenderingContext} gl context 226 | * @param {WebGLActiveInfo} attribute the attribute to bind to. 227 | * We use its name to grab the record by name and also to know 228 | * how many elements we need to grab. 229 | * @param {List} bufferElems The list coming in from Elm. 230 | * @param {Number} elemSize The length of the number of vertices that 231 | * complete one 'thing' based on the drawing mode. 232 | * ie, 2 for Lines, 3 for Triangles, etc. 233 | * @return {WebGLBuffer} 234 | */ 235 | function doBindAttribute(gl, attribute, bufferElems, elemSize) { 236 | var idxKeys = []; 237 | for (var i = 0; i < elemSize; i++) { 238 | idxKeys.push('_' + i); 239 | } 240 | 241 | function dataFill(data, cnt, fillOffset, elem, key) { 242 | if (elemSize === 1) { 243 | for (var i = 0; i < cnt; i++) { 244 | data[fillOffset++] = cnt === 1 ? elem[key] : elem[key][i]; 245 | } 246 | } else { 247 | idxKeys.forEach(function (idx) { 248 | for (var i = 0; i < cnt; i++) { 249 | data[fillOffset++] = cnt === 1 ? elem[idx][key] : elem[idx][key][i]; 250 | } 251 | }); 252 | } 253 | } 254 | 255 | var attributeInfo = getAttributeInfo(gl, attribute.type); 256 | 257 | if (attributeInfo === undefined) { 258 | throw new Error('No info available for: ' + attribute.type); 259 | } 260 | 261 | var dataIdx = 0; 262 | var array = new attributeInfo.type(listLength(bufferElems) * attributeInfo.size * elemSize); 263 | 264 | listEach(function (elem) { 265 | dataFill(array, attributeInfo.size, dataIdx, elem, attribute.name); 266 | dataIdx += attributeInfo.size * elemSize; 267 | }, bufferElems); 268 | 269 | var buffer = gl.createBuffer(); 270 | LOG('Created attribute buffer ' + attribute.name); 271 | 272 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 273 | gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW); 274 | return buffer; 275 | } 276 | 277 | /** 278 | * This sets up the binding caching buffers. 279 | * 280 | * We don't actually bind any buffers now except for the indices buffer. 281 | * The problem with filling the buffers here is that it is possible to 282 | * have a buffer shared between two webgl shaders; 283 | * which could have different active attributes. If we bind it here against 284 | * a particular program, we might not bind them all. That final bind is now 285 | * done right before drawing. 286 | * 287 | * @param {WebGLRenderingContext} gl context 288 | * @param {Object} renderType 289 | * @param {Number} renderType.indexSize size of the index 290 | * @param {Number} renderType.elemSize size of the element 291 | * @param {Drawable} drawable a drawable object from Elm 292 | * that contains elements and optionally indices 293 | * @return {Object} buffer - an object with the following properties 294 | * @return {Number} buffer.numIndices 295 | * @return {WebGLBuffer} buffer.indexBuffer 296 | * @return {Object} buffer.buffers - will be used to buffer attributes 297 | */ 298 | function doBindSetup(gl, renderType, drawable) { 299 | LOG('Created index buffer'); 300 | var indexBuffer = gl.createBuffer(); 301 | var indices = (renderType.indexSize === 0) 302 | ? makeSequentialBuffer(renderType.elemSize * listLength(drawable._0)) 303 | : makeIndexedBuffer(drawable._1, renderType.indexSize); 304 | 305 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); 306 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); 307 | 308 | return { 309 | numIndices: indices.length, 310 | indexBuffer: indexBuffer, 311 | buffers: {} 312 | }; 313 | } 314 | 315 | /** 316 | * Create an indices array and fill it with 0..n 317 | * 318 | * @param {Number} numIndices The number of indices 319 | * @return {Uint16Array} indices 320 | */ 321 | function makeSequentialBuffer(numIndices) { 322 | var indices = new Uint16Array(numIndices); 323 | for (var i = 0; i < numIndices; i++) { 324 | indices[i] = i; 325 | } 326 | return indices; 327 | } 328 | 329 | /** 330 | * Create an indices array and fill it from indices 331 | * based on the size of the index 332 | * 333 | * @param {List} indicesList the list of indices 334 | * @param {Number} indexSize the size of the index 335 | * @return {Uint16Array} indices 336 | */ 337 | function makeIndexedBuffer(indicesList, indexSize) { 338 | var indices = new Uint16Array(listLength(indicesList) * indexSize); 339 | var fillOffset = 0; 340 | var i; 341 | listEach(function (elem) { 342 | if (indexSize === 1) { 343 | indices[fillOffset++] = elem; 344 | } else { 345 | for (i = 0; i < indexSize; i++) { 346 | indices[fillOffset++] = elem['_' + i.toString()]; 347 | } 348 | } 349 | }, indicesList); 350 | return indices; 351 | } 352 | 353 | function getProgID(vertID, fragID) { 354 | return vertID + '#' + fragID; 355 | } 356 | 357 | function drawGL(domNode, data) { 358 | 359 | var model = data.model; 360 | var gl = model.cache.gl; 361 | 362 | if (!gl) { 363 | return domNode; 364 | } 365 | 366 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 367 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); 368 | LOG('Drawing'); 369 | 370 | function drawEntity(entity) { 371 | if (entity.buffer._0.ctor === '[]') { 372 | return; 373 | } 374 | 375 | var progid; 376 | var program; 377 | if (entity.vert.id && entity.frag.id) { 378 | progid = getProgID(entity.vert.id, entity.frag.id); 379 | program = model.cache.programs[progid]; 380 | } 381 | 382 | if (!program) { 383 | 384 | var vshader; 385 | if (entity.vert.id) { 386 | vshader = model.cache.shaders[entity.vert.id]; 387 | } else { 388 | entity.vert.id = guid(); 389 | } 390 | 391 | if (!vshader) { 392 | vshader = doCompile(gl, entity.vert.src, gl.VERTEX_SHADER); 393 | model.cache.shaders[entity.vert.id] = vshader; 394 | } 395 | 396 | var fshader; 397 | if (entity.frag.id) { 398 | fshader = model.cache.shaders[entity.frag.id]; 399 | } else { 400 | entity.frag.id = guid(); 401 | } 402 | 403 | if (!fshader) { 404 | fshader = doCompile(gl, entity.frag.src, gl.FRAGMENT_SHADER); 405 | model.cache.shaders[entity.frag.id] = fshader; 406 | } 407 | 408 | program = doLink(gl, vshader, fshader); 409 | progid = getProgID(entity.vert.id, entity.frag.id); 410 | model.cache.programs[progid] = program; 411 | 412 | } 413 | 414 | gl.useProgram(program); 415 | 416 | progid = progid || getProgID(entity.vert.id, entity.frag.id); 417 | var setters = model.cache.uniformSetters[progid]; 418 | if (!setters) { 419 | setters = createUniformSetters(gl, model, program); 420 | model.cache.uniformSetters[progid] = setters; 421 | } 422 | 423 | setUniforms(setters, entity.uniforms); 424 | 425 | var entityType = getRenderInfo(gl, entity.buffer.ctor); 426 | var buffer = model.cache.buffers[entity.buffer.guid]; 427 | 428 | if (!buffer) { 429 | buffer = doBindSetup(gl, entityType, entity.buffer); 430 | model.cache.buffers[entity.buffer.guid] = buffer; 431 | } 432 | 433 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.indexBuffer); 434 | 435 | var numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); 436 | 437 | for (var i = 0; i < numAttributes; i++) { 438 | var attribute = gl.getActiveAttrib(program, i); 439 | 440 | var attribLocation = gl.getAttribLocation(program, attribute.name); 441 | gl.enableVertexAttribArray(attribLocation); 442 | 443 | if (buffer.buffers[attribute.name] === undefined) { 444 | buffer.buffers[attribute.name] = doBindAttribute(gl, attribute, entity.buffer._0, entityType.elemSize); 445 | } 446 | var attributeBuffer = buffer.buffers[attribute.name]; 447 | var attributeInfo = getAttributeInfo(gl, attribute.type); 448 | 449 | gl.bindBuffer(gl.ARRAY_BUFFER, attributeBuffer); 450 | gl.vertexAttribPointer(attribLocation, attributeInfo.size, attributeInfo.baseType, false, 0, 0); 451 | } 452 | 453 | listEach(function (setting) { 454 | applySetting(gl, setting); 455 | }, entity.settings); 456 | 457 | gl.drawElements(entityType.mode, buffer.numIndices, gl.UNSIGNED_SHORT, 0); 458 | 459 | listEach(function (setting) { 460 | revertSetting(gl, setting); 461 | }, entity.settings); 462 | 463 | } 464 | 465 | listEach(drawEntity, model.entities); 466 | return domNode; 467 | } 468 | 469 | function createUniformSetters(gl, model, program) { 470 | var textureCounter = 0; 471 | function createUniformSetter(program, uniform) { 472 | var uniformLocation = gl.getUniformLocation(program, uniform.name); 473 | switch (uniform.type) { 474 | case gl.INT: 475 | return function (value) { 476 | gl.uniform1i(uniformLocation, value); 477 | }; 478 | case gl.FLOAT: 479 | return function (value) { 480 | gl.uniform1f(uniformLocation, value); 481 | }; 482 | case gl.FLOAT_VEC2: 483 | return function (value) { 484 | gl.uniform2fv(uniformLocation, new Float32Array(value)); 485 | }; 486 | case gl.FLOAT_VEC3: 487 | return function (value) { 488 | gl.uniform3fv(uniformLocation, new Float32Array(value)); 489 | }; 490 | case gl.FLOAT_VEC4: 491 | return function (value) { 492 | gl.uniform4fv(uniformLocation, new Float32Array(value)); 493 | }; 494 | case gl.FLOAT_MAT4: 495 | return function (value) { 496 | gl.uniformMatrix4fv(uniformLocation, false, new Float32Array(value)); 497 | }; 498 | case gl.SAMPLER_2D: 499 | var currentTexture = textureCounter++; 500 | return function (texture) { 501 | gl.activeTexture(gl.TEXTURE0 + currentTexture); 502 | var tex = model.cache.textures[texture.id]; 503 | if (!tex) { 504 | LOG('Created texture'); 505 | tex = texture.createTexture(gl); 506 | model.cache.textures[texture.id] = tex; 507 | } 508 | gl.bindTexture(gl.TEXTURE_2D, tex); 509 | gl.uniform1i(uniformLocation, currentTexture); 510 | }; 511 | case gl.BOOL: 512 | return function (value) { 513 | gl.uniform1i(uniformLocation, value); 514 | }; 515 | default: 516 | LOG('Unsupported uniform type: ' + uniform.type); 517 | return function () {}; 518 | } 519 | } 520 | 521 | var uniformSetters = {}; 522 | var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); 523 | for (var i = 0; i < numUniforms; i++) { 524 | var uniform = gl.getActiveUniform(program, i); 525 | uniformSetters[uniform.name] = createUniformSetter(program, uniform); 526 | } 527 | 528 | return uniformSetters; 529 | } 530 | 531 | function setUniforms(setters, values) { 532 | Object.keys(values).forEach(function (name) { 533 | var setter = setters[name]; 534 | if (setter) { 535 | setter(values[name]); 536 | } 537 | }); 538 | } 539 | 540 | // VIRTUAL-DOM WIDGET 541 | 542 | function toHtml(options, factList, entities) { 543 | var model = { 544 | entities: entities, 545 | cache: {}, 546 | options: options 547 | }; 548 | // eslint-disable-next-line camelcase 549 | return _elm_lang$virtual_dom$Native_VirtualDom.custom(factList, model, implementation); 550 | } 551 | 552 | var implementation = { 553 | render: render, 554 | diff: diff 555 | }; 556 | 557 | /** 558 | * Creates canvas and schedules initial drawGL 559 | * @param {Object} model 560 | * @param {Object} model.cache that may contain the following properties: 561 | gl, shaders, programs, uniformSetters, buffers, textures 562 | * @param {List