├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── examples ├── assets │ ├── css │ │ └── style.css │ ├── models │ │ ├── draco │ │ │ ├── goethe-lifemask │ │ │ │ ├── goeth-lifemask.drc │ │ │ │ └── normal-map.jpg │ │ │ └── mass │ │ │ │ └── mass.drc │ │ ├── json │ │ │ └── mass.json │ │ └── obj │ │ │ └── mass.obj │ ├── svg │ │ └── ixviii.svg │ ├── textures │ │ ├── cube │ │ │ ├── blackice │ │ │ │ ├── nx.jpg │ │ │ │ ├── ny.jpg │ │ │ │ ├── nz.jpg │ │ │ │ ├── px.jpg │ │ │ │ ├── py.jpg │ │ │ │ └── pz.jpg │ │ │ ├── pisa-hdr │ │ │ │ ├── nx.hdr │ │ │ │ ├── ny.hdr │ │ │ │ ├── nz.hdr │ │ │ │ ├── px.hdr │ │ │ │ ├── py.hdr │ │ │ │ └── pz.hdr │ │ │ └── pisa │ │ │ │ ├── nx.png │ │ │ │ ├── ny.png │ │ │ │ ├── nz.png │ │ │ │ ├── px.png │ │ │ │ ├── py.png │ │ │ │ └── pz.png │ │ ├── paint │ │ │ ├── license.txt │ │ │ ├── splatter-0.jpg │ │ │ ├── splatter-1.jpg │ │ │ └── splatter-2.jpg │ │ ├── pbr │ │ │ └── rusted-metal │ │ │ │ ├── About these PBR files.txt │ │ │ │ ├── albedo.png │ │ │ │ ├── ao.png │ │ │ │ ├── metalness.jpg │ │ │ │ └── normal.png │ │ ├── texture-nopow2-2.jpg │ │ ├── texture-nopow2.jpg │ │ ├── texture.jpg │ │ └── texture.mp4 │ └── third-party │ │ └── draco │ │ ├── draco_decoder.js │ │ ├── draco_decoder.wasm │ │ └── draco_wasm_wrapper.js ├── examples.json └── src │ ├── js │ ├── cameradolly │ │ ├── data0.json │ │ ├── data1.json │ │ └── index.js │ ├── decals │ │ ├── decal.js │ │ └── index.js │ ├── draco │ │ ├── DRACOLoader.js │ │ └── index.js │ ├── fractal.js │ ├── fullscreen │ │ └── index.js │ ├── gui.js │ ├── hdr │ │ └── index.js │ ├── hdrcube │ │ └── index.js │ ├── instancing │ │ └── index.js │ ├── lambert │ │ └── index.js │ ├── linegeometry │ │ └── index.js │ ├── memory │ │ └── index.js │ ├── obj │ │ └── index.js │ ├── pbr │ │ ├── index.js │ │ └── shader.glsl.js │ ├── phong │ │ └── index.js │ ├── points │ │ └── index.js │ ├── raycast │ │ └── index.js │ ├── rendertarget │ │ └── index.js │ ├── sandbox │ │ └── index.js │ ├── scenegraph │ │ └── index.js │ ├── shadows │ │ ├── ShadowMapRenderer.js │ │ └── index.js │ ├── stats.js │ ├── stereo │ │ ├── StereoRender.js │ │ └── index.js │ ├── template │ │ └── index.js │ ├── texture │ │ └── index.js │ ├── texture3d │ │ └── index.js │ ├── texture3d_advanced │ │ ├── distance-functions.js │ │ └── index.js │ ├── texturecube │ │ └── index.js │ ├── textureproj │ │ └── index.js │ ├── textureproj_advanced │ │ └── index.js │ ├── texturevideo │ │ └── index.js │ └── transformfeedback │ │ ├── TFMesh.js │ │ ├── index.js │ │ └── shaders.glsl.js │ └── templates │ ├── cameradolly.pug │ ├── decals.pug │ ├── draco.pug │ ├── fullscreen.pug │ ├── hdr.pug │ ├── hdrcube.pug │ ├── includes │ ├── _example.pug │ ├── _layout.pug │ └── _partials.pug │ ├── index.pug │ ├── instancing.pug │ ├── lambert.pug │ ├── linegeometry.pug │ ├── memory.pug │ ├── obj.pug │ ├── pbr.pug │ ├── phong.pug │ ├── points.pug │ ├── raycast.pug │ ├── rendertarget.pug │ ├── sandbox.pug │ ├── scenegraph.pug │ ├── shadows.pug │ ├── stereo.pug │ ├── template.pug │ ├── texture.pug │ ├── texture3d.pug │ ├── texture3d_advanced.pug │ ├── texturecube.pug │ ├── textureproj.pug │ ├── textureproj_advanced.pug │ ├── texturevideo.pug │ └── transformfeedback.pug ├── lib ├── ixviii.medium.js ├── ixviii.medium.js.map ├── ixviii.medium.min.js └── ixviii.medium.min.js.map ├── package.json ├── pug.config.js ├── src ├── cameras │ ├── Camera.ts │ ├── OrthographicCamera.ts │ └── PerspectiveCamera.ts ├── controls │ └── OrbitControls.ts ├── core │ ├── Capabilities.ts │ ├── Constants.ts │ ├── EventDispatcher.ts │ ├── GL.ts │ ├── ImageData.ts │ ├── Material.ts │ ├── Mesh.ts │ ├── Object3D.ts │ ├── Program.ts │ ├── Raycaster.ts │ ├── RenderTarget.ts │ ├── Renderer.ts │ ├── Scene.ts │ ├── Texture.ts │ ├── Texture3d.ts │ ├── TextureCube.ts │ ├── TextureVideo.ts │ ├── UniformBuffer.ts │ ├── UniformBuffers.ts │ └── Vao.ts ├── geometry │ ├── BoxGeometry.ts │ ├── BufferAttribute.ts │ ├── Face.ts │ ├── Geometry.ts │ ├── LineGeometry.ts │ ├── PlaneGeometry.ts │ └── SphereGeometry.ts ├── helpers │ ├── AxisHelper.ts │ ├── CameraHelper.ts │ ├── GridHelper.ts │ ├── NormalsHelper.ts │ └── VerticesHelper.ts ├── index.ts ├── lights │ ├── AmbientLight.ts │ ├── DirectionalLight.ts │ ├── Light.ts │ ├── Lights.ts │ └── PointLight.ts ├── loaders │ ├── FileLoader.ts │ ├── HdrLoader.ts │ ├── ImageLoader.ts │ ├── JsonLoader.ts │ └── ObjLoader.ts ├── math │ ├── Color.ts │ ├── Ray.ts │ ├── Sphere.ts │ ├── Utils.ts │ ├── Vector2.ts │ └── Vector3.ts ├── shaders │ ├── Basic.glsl.ts │ ├── Lambert.glsl.ts │ ├── Phong.glsl.ts │ ├── Vertex.glsl.ts │ └── chunks │ │ ├── AmbientLight.glsl.ts │ │ ├── Conditionals.glsl.ts │ │ ├── DirectionalLights.glsl.ts │ │ ├── EnvMapCube.glsl.ts │ │ ├── EsVersion.glsl.ts │ │ ├── Fog.glsl.ts │ │ ├── Gamma.glsl.ts │ │ ├── Lambert.glsl.ts │ │ ├── Matcap.glsl.ts │ │ ├── Math.glsl.ts │ │ ├── Noise.glsl.ts │ │ ├── Packing.glsl.ts │ │ ├── Phong.glsl.ts │ │ ├── PointLights.glsl.ts │ │ ├── ProjectionView.glsl.ts │ │ ├── Tonemap.glsl.ts │ │ ├── Transpose.glsl.ts │ │ └── index.ts └── utils │ ├── Array.ts │ ├── CameraDolly.ts │ ├── Canvas.ts │ ├── Clock.ts │ ├── Console.ts │ ├── Detect.ts │ ├── ObjParser.ts │ └── ShaderParser.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.build.js ├── webpack.config.examples.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs. 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # We recommend you to keep these unchanged. 9 | indent_style = space 10 | indent_size = 2 11 | tab_width = 2 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | 17 | [package.json] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["airbnb-base", "prettier"], 4 | "env": { 5 | "browser": true, 6 | "es6": true 7 | }, 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 6 11 | }, 12 | "rules": { 13 | "arrow-body-style": ["error", "as-needed"], 14 | "import/prefer-default-export": 0, 15 | "prefer-destructuring": 0, 16 | "import/no-named-as-default": 0, 17 | "comma-dangle": ["error", "never"], 18 | "no-param-reassign": 0, 19 | "no-bitwise": 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | .eslintcache 5 | examples/js/*.js.map 6 | examples/js/*.js 7 | examples/*.html 8 | .vscode 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | *.js.map 3 | /examples/js/*.js 4 | /examples/*.html 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Amelie Rosser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Medium 2 | 3 | Progressive WebGL toolkit for art. 4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | [![Status](https://david-dm.org/amelierosser/medium.svg)](https://david-dm.org/amelierosser/medium) 7 | [![devDependency Status](https://david-dm.org/amelierosser/medium/dev-status.svg)](https://david-dm.org/amelierosser/medium#info=devDependencies) 8 | -------------------------------------------------------------------------------- /examples/assets/css/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=PT+Serif'); 2 | 3 | html { 4 | font-size: 15px; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | font-family: 'PT Serif', serif; 10 | background-color: black; 11 | color: white; 12 | font-size: 1rem; 13 | -webkit-font-smoothing: antialiased; 14 | } 15 | 16 | .example { 17 | overflow: hidden; 18 | } 19 | 20 | h1 { 21 | font-size: 2.5rem; 22 | letter-spacing: 0.025em; 23 | margin: 0; 24 | } 25 | 26 | h3 { 27 | letter-spacing: 0.03em; 28 | } 29 | 30 | a { 31 | color: white; 32 | text-decoration: none; 33 | } 34 | 35 | a:hover { 36 | text-decoration: underline; 37 | } 38 | 39 | hr { 40 | border: 0; 41 | border-bottom: 1px solid #ffffff; 42 | } 43 | 44 | header { 45 | padding: 2rem; 46 | } 47 | 48 | .description { 49 | color: #bbbbbb; 50 | } 51 | 52 | .ixviii { 53 | width: 4rem; 54 | height: 4rem; 55 | background-size: 100%; 56 | background-image: url('../svg/ixviii.svg'); 57 | background-repeat: no-repeat; 58 | display: block; 59 | opacity: 0.5; 60 | transition: opacity 0.3s ease; 61 | position: absolute; 62 | bottom: 1.5rem; 63 | left: 1.5rem; 64 | } 65 | 66 | @media only screen and (max-width: 768px) { 67 | .index .ixviii { 68 | position: relative; 69 | margin-top: 2rem; 70 | bottom: 0; 71 | left: 0; 72 | } 73 | } 74 | 75 | .ixviii:hover { 76 | opacity: 1; 77 | } 78 | 79 | .nav { 80 | display: flex; 81 | flex-direction: row; 82 | flex-wrap: wrap; 83 | } 84 | 85 | .nav__item { 86 | flex: 0 auto; 87 | display: flex; 88 | flex-direction: column; 89 | min-width: 10rem; 90 | min-height: 5rem; 91 | } 92 | 93 | .nav__item ul { 94 | margin: 0; 95 | padding: 0; 96 | list-style: none; 97 | } 98 | 99 | .nav__item ul li { 100 | margin: 0.5rem 0; 101 | } 102 | 103 | #fps { 104 | background-color: black !important; 105 | } 106 | 107 | #fpsGraph > span { 108 | background-color: black !important; 109 | } 110 | 111 | #fpsText { 112 | color: white !important; 113 | } 114 | 115 | #fpsGraph { 116 | background-color: white !important; 117 | } 118 | -------------------------------------------------------------------------------- /examples/assets/models/draco/goethe-lifemask/goeth-lifemask.drc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/models/draco/goethe-lifemask/goeth-lifemask.drc -------------------------------------------------------------------------------- /examples/assets/models/draco/goethe-lifemask/normal-map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/models/draco/goethe-lifemask/normal-map.jpg -------------------------------------------------------------------------------- /examples/assets/models/draco/mass/mass.drc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/models/draco/mass/mass.drc -------------------------------------------------------------------------------- /examples/assets/textures/cube/blackice/nx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/blackice/nx.jpg -------------------------------------------------------------------------------- /examples/assets/textures/cube/blackice/ny.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/blackice/ny.jpg -------------------------------------------------------------------------------- /examples/assets/textures/cube/blackice/nz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/blackice/nz.jpg -------------------------------------------------------------------------------- /examples/assets/textures/cube/blackice/px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/blackice/px.jpg -------------------------------------------------------------------------------- /examples/assets/textures/cube/blackice/py.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/blackice/py.jpg -------------------------------------------------------------------------------- /examples/assets/textures/cube/blackice/pz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/blackice/pz.jpg -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa-hdr/nx.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa-hdr/nx.hdr -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa-hdr/ny.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa-hdr/ny.hdr -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa-hdr/nz.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa-hdr/nz.hdr -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa-hdr/px.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa-hdr/px.hdr -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa-hdr/py.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa-hdr/py.hdr -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa-hdr/pz.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa-hdr/pz.hdr -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa/nx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa/nx.png -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa/ny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa/ny.png -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa/nz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa/nz.png -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa/px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa/px.png -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa/py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa/py.png -------------------------------------------------------------------------------- /examples/assets/textures/cube/pisa/pz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/cube/pisa/pz.png -------------------------------------------------------------------------------- /examples/assets/textures/paint/license.txt: -------------------------------------------------------------------------------- 1 | http://www.fudgegraphics.com/2010/05/17-free-splatter-drips-vectors-illustrator/ 2 | 3 | "As always, you may use these freebies in both commercial and personal projects without the need of attribution. I hope you like them and put them to good use." 4 | -------------------------------------------------------------------------------- /examples/assets/textures/paint/splatter-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/paint/splatter-0.jpg -------------------------------------------------------------------------------- /examples/assets/textures/paint/splatter-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/paint/splatter-1.jpg -------------------------------------------------------------------------------- /examples/assets/textures/paint/splatter-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/paint/splatter-2.jpg -------------------------------------------------------------------------------- /examples/assets/textures/pbr/rusted-metal/About these PBR files.txt: -------------------------------------------------------------------------------- 1 | These texture files were created by FreePBR.com and may be used freely in your video games and 3d work at no cost. They may not however be redistributed on other websites or anywhere else other than FreePBR.com. We think that is more than fair. :) We also would greatly appreciate it if some sorrt of credit was given if you do indeed use these textures in a published game. Other than that, keep on creating and have fun. :) -------------------------------------------------------------------------------- /examples/assets/textures/pbr/rusted-metal/albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/pbr/rusted-metal/albedo.png -------------------------------------------------------------------------------- /examples/assets/textures/pbr/rusted-metal/ao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/pbr/rusted-metal/ao.png -------------------------------------------------------------------------------- /examples/assets/textures/pbr/rusted-metal/metalness.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/pbr/rusted-metal/metalness.jpg -------------------------------------------------------------------------------- /examples/assets/textures/pbr/rusted-metal/normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/pbr/rusted-metal/normal.png -------------------------------------------------------------------------------- /examples/assets/textures/texture-nopow2-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/texture-nopow2-2.jpg -------------------------------------------------------------------------------- /examples/assets/textures/texture-nopow2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/texture-nopow2.jpg -------------------------------------------------------------------------------- /examples/assets/textures/texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/texture.jpg -------------------------------------------------------------------------------- /examples/assets/textures/texture.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/textures/texture.mp4 -------------------------------------------------------------------------------- /examples/assets/third-party/draco/draco_decoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameliemaia/medium/9310e420e213b499774c52b9358e52275c3855b6/examples/assets/third-party/draco/draco_decoder.wasm -------------------------------------------------------------------------------- /examples/examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "Materials": [ 3 | { 4 | "id": "lambert", 5 | "title": "Lambert" 6 | }, 7 | { 8 | "id": "phong", 9 | "title": "Phong" 10 | } 11 | ], 12 | "Textures": [ 13 | { 14 | "id": "texture", 15 | "title": "Texture" 16 | }, 17 | { 18 | "id": "texturevideo", 19 | "title": "Texture Video" 20 | }, 21 | { 22 | "id": "texturecube", 23 | "title": "Texture Cube" 24 | }, 25 | { 26 | "id": "texture3d", 27 | "title": "Texture 3D" 28 | }, 29 | { 30 | "id": "texture3d_advanced", 31 | "title": "Texture 3D Advanced" 32 | }, 33 | { 34 | "id": "textureproj", 35 | "title": "Texture Projection" 36 | }, 37 | { 38 | "id": "textureproj_advanced", 39 | "title": "Texture Projection Advanced" 40 | }, 41 | { 42 | "id": "hdrcube", 43 | "title": "HDR Cube" 44 | } 45 | ], 46 | "Interaction": [ 47 | { 48 | "id": "raycast", 49 | "title": "Raycast" 50 | }, 51 | { 52 | "id": "decals", 53 | "title": "Decals" 54 | } 55 | ], 56 | "Camera": [ 57 | { 58 | "id": "cameradolly", 59 | "title": "Camera Dolly" 60 | } 61 | ], 62 | "Shadows": [ 63 | { 64 | "id": "shadows", 65 | "title": "Shadows" 66 | } 67 | ], 68 | "Geometry": [ 69 | { 70 | "id": "instancing", 71 | "title": "Instancing" 72 | } 73 | ], 74 | "File formats": [ 75 | { 76 | "id": "draco", 77 | "title": "Draco" 78 | }, 79 | { 80 | "id": "obj", 81 | "title": "Obj" 82 | } 83 | ], 84 | "Misc": [ 85 | { 86 | "id": "sandbox", 87 | "title": "Sandbox" 88 | }, 89 | { 90 | "id": "scenegraph", 91 | "title": "Scene graph" 92 | }, 93 | { 94 | "id": "points", 95 | "title": "Points" 96 | }, 97 | { 98 | "id": "memory", 99 | "title": "Memory" 100 | }, 101 | { 102 | "id": "fullscreen", 103 | "title": "Fullscreen shader" 104 | } 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /examples/src/js/cameradolly/data0.json: -------------------------------------------------------------------------------- 1 | { 2 | "origin": [ 3 | { 4 | "x": -10, 5 | "y": 2, 6 | "z": 10 7 | }, 8 | { 9 | "x": 0, 10 | "y": 0, 11 | "z": 20 12 | }, 13 | { 14 | "x": 10, 15 | "y": 2, 16 | "z": 10 17 | } 18 | ], 19 | "lookat": [ 20 | { 21 | "x": -10, 22 | "y": 10, 23 | "z": 0 24 | }, 25 | { 26 | "x": 0, 27 | "y": 0, 28 | "z": 0 29 | }, 30 | { 31 | "x": 10, 32 | "y": 10, 33 | "z": 0 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /examples/src/js/cameradolly/data1.json: -------------------------------------------------------------------------------- 1 | { 2 | "origin": [ 3 | { 4 | "x": -10, 5 | "y": 12, 6 | "z": 10 7 | }, 8 | { 9 | "x": 0, 10 | "y": 10, 11 | "z": 20 12 | }, 13 | { 14 | "x": 10, 15 | "y": 12, 16 | "z": 10 17 | } 18 | ], 19 | "lookat": [ 20 | { 21 | "x": -10, 22 | "y": 20, 23 | "z": 0 24 | }, 25 | { 26 | "x": 0, 27 | "y": 10, 28 | "z": 0 29 | }, 30 | { 31 | "x": 10, 32 | "y": 20, 33 | "z": 0 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /examples/src/js/cameradolly/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | GridHelper, 6 | OrbitControls, 7 | AxisHelper, 8 | CameraHelper, 9 | CameraDolly 10 | } from '../../../../src/index.ts'; 11 | import data0 from './data0.json'; 12 | import data1 from './data1.json'; 13 | import stats from '../stats'; 14 | 15 | const { gui, guiController } = require('../gui')(); 16 | 17 | guiController.debug = true; 18 | gui.add(guiController, 'debug'); 19 | 20 | // Renderer 21 | const renderer = new Renderer({ 22 | ratio: window.innerWidth / window.innerHeight, 23 | prefferedContext: guiController.context 24 | }); 25 | renderer.setDevicePixelRatio(window.devicePixelRatio); 26 | renderer.setSissorTest(true); 27 | document.body.appendChild(renderer.canvas); 28 | 29 | // Scene 30 | const scene = new Scene(); 31 | 32 | // Camera 33 | const cameras = { 34 | dev: new PerspectiveCamera({ 35 | fov: 45, 36 | far: 500, 37 | ratio: window.innerWidth / window.innerHeight 38 | }), 39 | main: new PerspectiveCamera({ 40 | fov: 45, 41 | far: 500, 42 | ratio: window.innerWidth / window.innerHeight 43 | }) 44 | }; 45 | 46 | const zoom = 5; 47 | cameras.dev.position.set(10 * zoom, 5 * zoom, 10 * zoom); 48 | cameras.dev.lookAt(); 49 | 50 | cameras.main.position.set(10 * zoom, 5 * zoom, 10 * zoom); 51 | cameras.main.lookAt(); 52 | 53 | const cameraDolly = new CameraDolly({ 54 | camera: cameras.main, 55 | scene, 56 | gui, 57 | curveSteps: 50, 58 | guiSliderRange: 100, 59 | guiOpen: false, 60 | guiOpenOrigin: false, 61 | guiOpenLookat: false 62 | }); 63 | cameraDolly.add('0', data0); 64 | cameraDolly.add('1', data1); 65 | 66 | gui.add(cameraDolly, 'destroy'); 67 | 68 | const keys = Object.keys(cameraDolly.dollies); 69 | 70 | gui.add(cameraDolly, 'dolly', keys).onChange(() => { 71 | cameraDolly.update(); 72 | }); 73 | 74 | gui.add(cameraDolly, 'time', 0, 1).onChange(() => { 75 | cameraDolly.update(); 76 | }); 77 | 78 | cameraDolly.update(); 79 | 80 | // Helpers 81 | const controls = new OrbitControls(cameras.dev, renderer.canvas); 82 | 83 | const grid = new GridHelper(10); 84 | scene.add(grid); 85 | 86 | const axis = new AxisHelper(1); 87 | scene.add(axis); 88 | 89 | const cameraHelper = new CameraHelper(cameras.main); 90 | scene.add(cameraHelper); 91 | 92 | controls.update(); 93 | 94 | function resize() { 95 | const width = window.innerWidth; 96 | const height = window.innerHeight; 97 | renderer.setSize(width, height); 98 | cameras.dev.aspect = width / height; 99 | cameras.dev.updateProjectionMatrix(); 100 | cameras.main.aspect = width / height; 101 | cameras.main.updateProjectionMatrix(); 102 | } 103 | resize(); 104 | 105 | window.addEventListener('resize', resize); 106 | 107 | function render(camera, x, y, width, height) { 108 | renderer.setSissor( 109 | x, 110 | y, 111 | width * window.innerWidth, 112 | height * window.innerHeight 113 | ); 114 | renderer.setViewport( 115 | x, 116 | y, 117 | width * window.innerWidth, 118 | height * window.innerHeight 119 | ); 120 | renderer.render(scene, camera); 121 | } 122 | 123 | function update() { 124 | requestAnimationFrame(update); 125 | 126 | stats.begin(); 127 | 128 | cameraHelper.update(); 129 | 130 | cameras.dev.updateMatrixWorld(); 131 | cameras.main.updateMatrixWorld(); 132 | 133 | if (guiController.debug) { 134 | render(cameras.dev, 0, 0, 1, 1); 135 | render(cameras.main, 0, 0, 0.25, 0.25); 136 | } else { 137 | render(cameras.main, 0, 0, 1, 1); 138 | } 139 | 140 | stats.end(); 141 | } 142 | update(); 143 | -------------------------------------------------------------------------------- /examples/src/js/draco/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | GridHelper, 6 | OrbitControls, 7 | AxisHelper, 8 | Lights, 9 | DirectionalLight, 10 | AmbientLight, 11 | Material, 12 | Color, 13 | Mesh 14 | } from '../../../../src/index.ts'; 15 | import DRACOLoader from './DRACOLoader'; 16 | import stats from '../stats'; 17 | 18 | const { guiController } = require('../gui')(); 19 | 20 | // Renderer 21 | const renderer = new Renderer({ 22 | ratio: window.innerWidth / window.innerHeight, 23 | prefferedContext: guiController.context 24 | }); 25 | renderer.setDevicePixelRatio(window.devicePixelRatio); 26 | document.body.appendChild(renderer.canvas); 27 | 28 | // Scene 29 | const scene = new Scene(); 30 | 31 | // Camera 32 | const camera = new PerspectiveCamera({ 33 | fov: 45, 34 | far: 500, 35 | ratio: window.innerWidth / window.innerHeight 36 | }); 37 | 38 | camera.position.set(10, 5, 10); 39 | camera.lookAt(); 40 | 41 | // Helpers 42 | const controls = new OrbitControls(camera, renderer.canvas); 43 | controls.smoothing = true; 44 | 45 | const grid = new GridHelper(10); 46 | scene.add(grid); 47 | 48 | const axis = new AxisHelper(1); 49 | scene.add(axis); 50 | 51 | controls.update(); 52 | 53 | // Lights 54 | 55 | const ambientLight = new Lights([ 56 | new AmbientLight({ 57 | intensity: { 58 | type: 'f', 59 | value: 0.5 60 | }, 61 | color: { 62 | type: '3f', 63 | value: new Color(0x404040).v 64 | } 65 | }) 66 | ]); 67 | 68 | const directionalLights = new Lights([ 69 | new DirectionalLight({ 70 | intensity: { 71 | type: 'f', 72 | value: 0.7 73 | }, 74 | color: { 75 | type: '3f', 76 | value: new Color(0xffffff).v 77 | } 78 | }) 79 | ]); 80 | 81 | directionalLights.get()[0].position.set(1, 1, 1); 82 | 83 | scene.ambientLight = ambientLight; 84 | scene.directionalLights = directionalLights; 85 | 86 | // Draco 87 | 88 | const dracoLoader = new DRACOLoader(undefined, 'assets/third-party/draco'); 89 | 90 | dracoLoader.setVerbosity(1); 91 | 92 | dracoLoader.load('assets/models/draco/mass/mass.drc', geometry => { 93 | const mesh = new Mesh( 94 | geometry, 95 | new Material({ 96 | directionalLights, 97 | ambientLight, 98 | type: 'lambert', 99 | uniforms: { 100 | uDiffuse: { 101 | type: '3f', 102 | value: new Color(0xffffff).v 103 | } 104 | } 105 | }) 106 | ); 107 | 108 | scene.add(mesh); 109 | }); 110 | 111 | function resize() { 112 | const width = window.innerWidth; 113 | const height = window.innerHeight; 114 | renderer.setSize(width, height); 115 | camera.aspect = width / height; 116 | camera.updateProjectionMatrix(); 117 | } 118 | resize(); 119 | 120 | window.addEventListener('resize', resize); 121 | 122 | function update() { 123 | requestAnimationFrame(update); 124 | 125 | stats.begin(); 126 | 127 | controls.update(); 128 | 129 | camera.updateMatrixWorld(); 130 | renderer.render(scene, camera); 131 | 132 | stats.end(); 133 | } 134 | update(); 135 | -------------------------------------------------------------------------------- /examples/src/js/fractal.js: -------------------------------------------------------------------------------- 1 | import { vec3 } from 'gl-matrix'; 2 | 3 | export class Sierpinski { 4 | generate( 5 | size = 20, 6 | iterations = 2, 7 | grid = 3, 8 | holes = [4, 10, 12, 13, 14, 16, 22], 9 | ) { 10 | this.size = size; 11 | this.grid = grid; 12 | this.iterations = iterations; 13 | this.holes = holes; 14 | const divisor = 1 / this.grid; 15 | let cubeSize = this.size * divisor; 16 | let positions = [vec3.create()]; 17 | 18 | let tmp; 19 | for (let i = 0; i < this.iterations; i += 1) { 20 | tmp = []; 21 | 22 | for (let j = 0; j < positions.length; j += 1) { 23 | const position = positions[j]; 24 | tmp = tmp.concat( 25 | this.sponge(position, cubeSize, this.grid, this.holes), 26 | ); 27 | } 28 | 29 | cubeSize *= divisor; 30 | 31 | positions = tmp; 32 | } 33 | 34 | const centerOffset = this.size / 2 - this.logarithmicScale() / 2; 35 | 36 | positions.forEach(position => { 37 | position[0] -= centerOffset; 38 | position[1] -= centerOffset; 39 | position[2] -= centerOffset; 40 | }); 41 | 42 | return positions; 43 | } 44 | 45 | sponge(position, cubeSize, grid, holes) { 46 | let i = 0; 47 | const positions = []; 48 | 49 | for (let levels = 0; levels < grid; levels += 1) { 50 | for (let rows = 0; rows < grid; rows += 1) { 51 | for (let columns = 0; columns < grid; columns += 1) { 52 | if (!this.inList(i, holes)) { 53 | const positionNew = vec3.create(); 54 | positionNew[0] = position[0] + rows * cubeSize; 55 | positionNew[1] = position[1] + levels * cubeSize; 56 | positionNew[2] = position[2] + columns * cubeSize; 57 | positions.push(positionNew); 58 | } 59 | i += 1; 60 | } 61 | } 62 | } 63 | 64 | return positions; 65 | } 66 | 67 | inList = (val, list) => { 68 | let result = false; 69 | let item; 70 | 71 | for (let i = 0; i < list.length; i += 1) { 72 | item = list[i]; 73 | if (item === val) { 74 | result = true; 75 | break; 76 | } 77 | } 78 | return result; 79 | }; 80 | 81 | logarithmicScale() { 82 | return this.size / this.grid ** this.iterations; 83 | } 84 | } 85 | 86 | export const jerusalem = [ 87 | 7, 88 | 11, 89 | 12, 90 | 13, 91 | 17, 92 | 27, 93 | 32, 94 | 35, 95 | 36, 96 | 37, 97 | 38, 98 | 39, 99 | 42, 100 | 47, 101 | 51, 102 | 52, 103 | 53, 104 | 55, 105 | 56, 106 | 57, 107 | 58, 108 | 59, 109 | 60, 110 | 61, 111 | 62, 112 | 63, 113 | 64, 114 | 65, 115 | 66, 116 | 67, 117 | 68, 118 | 69, 119 | 71, 120 | 72, 121 | 73, 122 | 77, 123 | 81, 124 | 82, 125 | 83, 126 | 85, 127 | 86, 128 | 87, 129 | 88, 130 | 89, 131 | 91, 132 | 92, 133 | 93, 134 | 97, 135 | 107, 136 | 111, 137 | 112, 138 | 113, 139 | 117 140 | ]; 141 | -------------------------------------------------------------------------------- /examples/src/js/fullscreen/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | OrthographicCamera, 5 | Mesh, 6 | Material, 7 | PlaneGeometry 8 | } from '../../../../src/index.ts'; 9 | import stats from '../stats'; 10 | 11 | const { guiController } = require('../gui')(); 12 | 13 | // Renderer 14 | const renderer = new Renderer({ 15 | ratio: window.innerWidth / window.innerHeight, 16 | prefferedContext: guiController.context 17 | }); 18 | renderer.setDevicePixelRatio(window.devicePixelRatio); 19 | document.body.appendChild(renderer.canvas); 20 | 21 | // Scene 22 | const scene = new Scene(); 23 | 24 | // Camera 25 | // const camera = new PerspectiveCamera(); 26 | const camera = new OrthographicCamera(); 27 | camera.position.set(0, 0, 1); 28 | camera.lookAt(); 29 | 30 | const geometry = new PlaneGeometry(2, 2); 31 | const material = new Material({ 32 | name: 'Plane', 33 | hookFragmentEnd: 'outgoingColor = vec4(vUv, 1.0, 1.0);' 34 | }); 35 | 36 | const plane = new Mesh(geometry, material); 37 | 38 | scene.add(plane); 39 | 40 | function resize() { 41 | const width = window.innerWidth; 42 | const height = window.innerHeight; 43 | renderer.setSize(width, height); 44 | camera.aspect = width / height; 45 | camera.updateProjectionMatrix(); 46 | } 47 | resize(); 48 | 49 | window.addEventListener('resize', resize); 50 | 51 | function update() { 52 | requestAnimationFrame(update); 53 | 54 | stats.begin(); 55 | 56 | camera.updateMatrixWorld(); 57 | 58 | renderer.render(scene, camera); 59 | 60 | stats.end(); 61 | } 62 | update(); 63 | -------------------------------------------------------------------------------- /examples/src/js/gui.js: -------------------------------------------------------------------------------- 1 | import dat from 'dat-gui'; 2 | import queryString from 'query-string'; 3 | 4 | const gui = new dat.GUI(); 5 | // dat.GUI.toggleHide(); 6 | 7 | module.exports = modes => { 8 | const options = modes !== undefined ? modes : ['webgl2', 'webgl']; 9 | 10 | const queries = queryString.parse(window.location.search); 11 | 12 | function getQuery(query) { 13 | return queries[query]; 14 | } 15 | 16 | const setQuery = (query, val) => { 17 | const newQueries = Object.assign({}, queries, { 18 | [query]: val 19 | }); 20 | const stringified = queryString.stringify(newQueries); 21 | 22 | const url = `${window.location.pathname}?${stringified}`; 23 | window.location.href = url; 24 | }; 25 | 26 | const guiController = { 27 | context: getQuery('context') || options[0] 28 | }; 29 | 30 | gui.add(guiController, 'context', options).onChange(val => { 31 | setQuery('context', val); 32 | }); 33 | 34 | return { 35 | gui, 36 | guiController, 37 | getQuery, 38 | setQuery 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /examples/src/js/hdr/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | Mesh, 6 | Material, 7 | PlaneGeometry, 8 | GridHelper, 9 | OrbitControls, 10 | AxisHelper, 11 | Texture 12 | } from '../../../../src/index.ts'; 13 | import stats from '../stats'; 14 | 15 | const { guiController } = require('../gui')(['webgl2']); 16 | 17 | // Renderer 18 | const renderer = new Renderer({ 19 | ratio: window.innerWidth / window.innerHeight, 20 | prefferedContext: guiController.context 21 | }); 22 | renderer.setDevicePixelRatio(window.devicePixelRatio); 23 | document.body.appendChild(renderer.canvas); 24 | 25 | // Scene 26 | const scene = new Scene(); 27 | 28 | // Camera 29 | const camera = new PerspectiveCamera({ 30 | fov: 45 31 | }); 32 | 33 | camera.position.set(10, 5, 10); 34 | camera.lookAt(); 35 | 36 | // Objects 37 | const hdrMap = new Texture({ 38 | src: 'assets/textures/cube/pisa-hdr/nx.hdr' 39 | }); 40 | 41 | const geometry = new PlaneGeometry(1, 1); 42 | const material = new Material({ 43 | name: 'Plane', 44 | hookFragmentPre: ` 45 | uniform sampler2D uMap; 46 | `, 47 | hookFragmentMain: ` 48 | color = texture(uMap, vUv).rgb; 49 | `, 50 | uniforms: { 51 | uMap: { 52 | type: 't', 53 | value: hdrMap.texture 54 | } 55 | } 56 | }); 57 | 58 | const plane = new Mesh(geometry, material); 59 | scene.add(plane); 60 | 61 | // Helpers 62 | const controls = new OrbitControls(camera, renderer.canvas); 63 | 64 | const grid = new GridHelper(10); 65 | scene.add(grid); 66 | // 67 | const axis = new AxisHelper(1); 68 | scene.add(axis); 69 | 70 | controls.update(); 71 | 72 | function resize() { 73 | const width = window.innerWidth; 74 | const height = window.innerHeight; 75 | renderer.setSize(width, height); 76 | camera.aspect = width / height; 77 | camera.updateProjectionMatrix(); 78 | } 79 | resize(); 80 | 81 | window.addEventListener('resize', resize); 82 | 83 | function update() { 84 | requestAnimationFrame(update); 85 | stats.begin(); 86 | camera.updateMatrixWorld(); 87 | renderer.render(scene, camera); 88 | stats.end(); 89 | } 90 | update(); 91 | -------------------------------------------------------------------------------- /examples/src/js/instancing/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | Mesh, 6 | Material, 7 | BoxGeometry, 8 | OrbitControls, 9 | Color, 10 | DirectionalLight, 11 | ShaderChunks, 12 | Lights 13 | } from '../../../../src/index.ts'; 14 | import { Sierpinski, jerusalem } from '../fractal'; 15 | import stats from '../stats'; 16 | 17 | const { gui, guiController } = require('../gui')(); 18 | 19 | // Renderer 20 | const renderer = new Renderer({ 21 | ratio: window.innerWidth / window.innerHeight, 22 | prefferedContext: guiController.context 23 | }); 24 | renderer.setDevicePixelRatio(window.devicePixelRatio); 25 | document.body.appendChild(renderer.canvas); 26 | 27 | // Scene 28 | const scene = new Scene(); 29 | 30 | // Camera 31 | const camera = new PerspectiveCamera({ 32 | fov: 45, 33 | far: 500 34 | }); 35 | 36 | camera.position.set(0, 0, 50); 37 | camera.lookAt(); 38 | 39 | // Helpers 40 | const controls = new OrbitControls(camera, renderer.canvas); 41 | 42 | controls.update(); 43 | 44 | // Content 45 | 46 | const directionalLights = new Lights([ 47 | new DirectionalLight({ 48 | intensity: { 49 | type: 'f', 50 | value: 1 51 | }, 52 | color: { 53 | type: '3f', 54 | value: new Color(0xcccccc).v 55 | } 56 | }) 57 | ]); 58 | 59 | directionalLights.get()[0].position.set(0.1, 1, 0.1); 60 | 61 | scene.directionalLights = directionalLights; 62 | 63 | const sierpinski = new Sierpinski(); 64 | 65 | const positions = sierpinski.generate(40, 2, 5, jerusalem); 66 | 67 | const totalInstances = positions.length; 68 | const data = new Float32Array(totalInstances * 3); 69 | let i3 = 0; 70 | for (let i = 0; i < totalInstances; i += 1) { 71 | i3 = i * 3; 72 | data[i3] = positions[i][0]; 73 | data[i3 + 1] = positions[i][1]; 74 | data[i3 + 2] = positions[i][2]; 75 | } 76 | 77 | console.log('instances', data.length / 3); // eslint-disable-line no-console 78 | 79 | const size = sierpinski.logarithmicScale() / 2; 80 | const geometry = new BoxGeometry(size, size, size); 81 | geometry.addInstancedBufferAttribute('aOffset', data, 3); 82 | 83 | const mesh = new Mesh( 84 | geometry, 85 | new Material({ 86 | type: 'lambert', 87 | uniforms: { 88 | uDiffuse: { 89 | type: '3f', 90 | value: new Color(0xffffff).v 91 | }, 92 | uFogDensity: { 93 | type: 'f', 94 | value: 0.016 95 | } 96 | }, 97 | hookVertexPre: ` 98 | in vec3 aOffset; 99 | uniform float uFogDensity; 100 | out float vFogAmount; 101 | ${ShaderChunks.Fog.exp2} 102 | `, 103 | hookVertexMain: ` 104 | transformed = aOffset; 105 | `, 106 | hookVertexEnd: ` 107 | float fogDistance = length(gl_Position.xyz); 108 | vFogAmount = fogExp2(fogDistance, uFogDensity); 109 | `, 110 | hookFragmentPre: ` 111 | in float vFogAmount; 112 | `, 113 | hookFragmentEnd: ` 114 | vec3 fogColor = vec3(0.0); 115 | outgoingColor = vec4(mix(color, fogColor, vFogAmount), 1.0); 116 | `, 117 | directionalLights 118 | }) 119 | ); 120 | 121 | mesh.setInstanceCount(totalInstances); 122 | 123 | scene.add(mesh); 124 | 125 | gui 126 | .add(mesh.material.uniforms.uFogDensity, 'value', 0, 0.1) 127 | .name('fog density'); 128 | 129 | function resize() { 130 | const width = window.innerWidth; 131 | const height = window.innerHeight; 132 | renderer.setSize(width, height); 133 | camera.aspect = width / height; 134 | camera.updateProjectionMatrix(); 135 | } 136 | resize(); 137 | 138 | window.addEventListener('resize', resize); 139 | 140 | function update() { 141 | requestAnimationFrame(update); 142 | stats.begin(); 143 | camera.updateMatrixWorld(); 144 | renderer.render(scene, camera); 145 | stats.end(); 146 | } 147 | update(); 148 | -------------------------------------------------------------------------------- /examples/src/js/lambert/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | GridHelper, 6 | OrbitControls, 7 | AxisHelper, 8 | Lights, 9 | AmbientLight, 10 | DirectionalLight, 11 | Color, 12 | Mesh, 13 | SphereGeometry, 14 | Material 15 | } from '../../../../src/index.ts'; 16 | import stats from '../stats'; 17 | 18 | const { guiController } = require('../gui')(); 19 | 20 | // Renderer 21 | const renderer = new Renderer({ 22 | ratio: window.innerWidth / window.innerHeight, 23 | prefferedContext: guiController.context 24 | }); 25 | renderer.setDevicePixelRatio(window.devicePixelRatio); 26 | document.body.appendChild(renderer.canvas); 27 | 28 | // Scene 29 | const scene = new Scene(); 30 | 31 | // Camera 32 | const camera = new PerspectiveCamera({ 33 | fov: 45, 34 | far: 500, 35 | ratio: window.innerWidth / window.innerHeight 36 | }); 37 | 38 | camera.position.set(10, 5, 10); 39 | camera.lookAt(); 40 | 41 | // Helpers 42 | const controls = new OrbitControls(camera, renderer.canvas); 43 | controls.smoothing = true; 44 | 45 | const grid = new GridHelper(10); 46 | scene.add(grid); 47 | 48 | const axis = new AxisHelper(1); 49 | scene.add(axis); 50 | 51 | controls.update(); 52 | 53 | const ambientLight = new Lights([ 54 | new AmbientLight({ 55 | intensity: { 56 | type: 'f', 57 | value: 0.5 58 | }, 59 | color: { 60 | type: '3f', 61 | value: new Color(0x404040).v 62 | } 63 | }) 64 | ]); 65 | 66 | const directionalLights = new Lights([ 67 | new DirectionalLight({ 68 | intensity: { 69 | type: 'f', 70 | value: 0.7 71 | }, 72 | color: { 73 | type: '3f', 74 | value: new Color(0xffffff).v 75 | } 76 | }) 77 | ]); 78 | 79 | directionalLights.get()[0].position.set(1, 1, 1); 80 | 81 | scene.ambientLight = ambientLight; 82 | scene.directionalLights = directionalLights; 83 | 84 | // Objects 85 | const mesh = new Mesh( 86 | new SphereGeometry(2, 32, 32), 87 | new Material({ 88 | directionalLights, 89 | ambientLight, 90 | type: 'lambert', 91 | uniforms: { 92 | uDiffuse: { 93 | type: '3f', 94 | value: new Color(0xffffff).v 95 | } 96 | } 97 | }) 98 | ); 99 | scene.add(mesh); 100 | 101 | function resize() { 102 | const width = window.innerWidth; 103 | const height = window.innerHeight; 104 | renderer.setSize(width, height); 105 | camera.aspect = width / height; 106 | camera.updateProjectionMatrix(); 107 | } 108 | resize(); 109 | 110 | window.addEventListener('resize', resize); 111 | 112 | function update() { 113 | requestAnimationFrame(update); 114 | 115 | stats.begin(); 116 | 117 | camera.updateMatrixWorld(); 118 | controls.update(); 119 | 120 | renderer.render(scene, camera); 121 | 122 | stats.end(); 123 | } 124 | update(); 125 | -------------------------------------------------------------------------------- /examples/src/js/linegeometry/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | GridHelper, 6 | OrbitControls, 7 | AxisHelper, 8 | Mesh, 9 | LineGeometry, 10 | Material, 11 | Constants 12 | } from '../../../../src/index.ts'; 13 | import stats from '../stats'; 14 | 15 | const { guiController } = require('../gui')(); 16 | 17 | // Renderer 18 | const renderer = new Renderer({ 19 | ratio: window.innerWidth / window.innerHeight, 20 | prefferedContext: guiController.context 21 | }); 22 | renderer.setDevicePixelRatio(window.devicePixelRatio); 23 | document.body.appendChild(renderer.canvas); 24 | 25 | // Scene 26 | const scene = new Scene(); 27 | 28 | // Camera 29 | const camera = new PerspectiveCamera({ 30 | fov: 45, 31 | far: 500 32 | }); 33 | 34 | camera.position.set(10, 5, 10); 35 | camera.lookAt(); 36 | 37 | // Helpers 38 | const controls = new OrbitControls(camera, renderer.canvas); 39 | 40 | const grid = new GridHelper(10); 41 | scene.add(grid); 42 | 43 | const axis = new AxisHelper(1); 44 | scene.add(axis); 45 | 46 | controls.update(); 47 | 48 | // Create curve 49 | const points = [ 50 | ...[0, 0, 0], 51 | ...[3, 5, 6], 52 | ...[5, 6, 7], 53 | ...[9, 9, 9], 54 | ...[9, 2, 1], 55 | ...[2, 4, 1] 56 | ]; 57 | const bufferVertices = new Float32Array(points); 58 | 59 | const mesh = new Mesh( 60 | new LineGeometry(bufferVertices), 61 | new Material({ 62 | drawType: Constants.DRAW_LINES 63 | }) 64 | ); 65 | scene.add(mesh); 66 | 67 | function resize() { 68 | const width = window.innerWidth; 69 | const height = window.innerHeight; 70 | renderer.setSize(width, height); 71 | camera.aspect = width / height; 72 | camera.updateProjectionMatrix(); 73 | } 74 | resize(); 75 | 76 | window.addEventListener('resize', resize); 77 | 78 | function update() { 79 | requestAnimationFrame(update); 80 | stats.begin(); 81 | camera.updateMatrixWorld(); 82 | renderer.render(scene, camera); 83 | stats.end(); 84 | } 85 | update(); 86 | -------------------------------------------------------------------------------- /examples/src/js/memory/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | Mesh, 6 | Material, 7 | BoxGeometry, 8 | OrbitControls, 9 | Texture 10 | } from '../../../../src/index.ts'; 11 | import stats from '../stats'; 12 | 13 | const { guiController } = require('../gui')(); 14 | 15 | // Renderer 16 | const renderer = new Renderer({ 17 | ratio: window.innerWidth / window.innerHeight, 18 | prefferedContext: guiController.context 19 | }); 20 | renderer.setDevicePixelRatio(window.devicePixelRatio); 21 | document.body.appendChild(renderer.canvas); 22 | 23 | // Scene 24 | const scene = new Scene(); 25 | 26 | // Camera 27 | const camera = new PerspectiveCamera({ 28 | fov: 45 29 | }); 30 | 31 | camera.position.set(10, 5, 10); 32 | camera.lookAt(); 33 | 34 | // Helpers 35 | const controls = new OrbitControls(camera, renderer.canvas); 36 | 37 | controls.update(); 38 | 39 | let boxes = []; 40 | 41 | function removeBox() { 42 | scene.remove(boxes[0], true); 43 | boxes = []; 44 | } 45 | 46 | let count = 0; 47 | 48 | function addBox() { 49 | const texture = new Texture({ 50 | src: 'assets/textures/texture-nopow2.jpg' 51 | }); 52 | const geometry = new BoxGeometry(1, 1, 1); 53 | const material = new Material({ 54 | name: 'Box', 55 | hookFragmentPre: ` 56 | uniform sampler2D uTexture0; 57 | `, 58 | hookFragmentMain: ` 59 | color = texture(uTexture0, vUv).rgb; 60 | `, 61 | uniforms: { 62 | uTexture0: { 63 | type: 't', 64 | value: texture.texture 65 | } 66 | } 67 | }); 68 | const box = new Mesh(geometry, material); 69 | box.rotation.x = Math.random(); 70 | box.rotation.y = Math.random(); 71 | box.rotation.z = Math.random(); 72 | scene.add(box); 73 | boxes.push(box); 74 | 75 | count += 1; 76 | 77 | console.log('boxes', count); // eslint-disable-line no-console 78 | } 79 | 80 | setInterval(() => { 81 | removeBox(); 82 | addBox(); 83 | }, 1000); 84 | 85 | addBox(); 86 | 87 | function resize() { 88 | const width = window.innerWidth; 89 | const height = window.innerHeight; 90 | renderer.setSize(width, height); 91 | camera.aspect = width / height; 92 | camera.updateProjectionMatrix(); 93 | } 94 | resize(); 95 | 96 | window.addEventListener('resize', resize); 97 | 98 | function update() { 99 | requestAnimationFrame(update); 100 | stats.begin(); 101 | camera.updateMatrixWorld(); 102 | renderer.render(scene, camera); 103 | stats.end(); 104 | } 105 | update(); 106 | -------------------------------------------------------------------------------- /examples/src/js/obj/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | OrbitControls, 6 | ObjLoader, 7 | Geometry, 8 | Material, 9 | Mesh, 10 | Color, 11 | PointLight, 12 | Lights, 13 | AmbientLight, 14 | DirectionalLight 15 | } from '../../../../src/index.ts'; 16 | import stats from '../stats'; 17 | 18 | const { guiController } = require('../gui')(); 19 | 20 | // Renderer 21 | const renderer = new Renderer({ 22 | ratio: window.innerWidth / window.innerHeight, 23 | prefferedContext: guiController.context 24 | }); 25 | renderer.setDevicePixelRatio(window.devicePixelRatio); 26 | document.body.appendChild(renderer.canvas); 27 | 28 | // Scene 29 | const scene = new Scene(); 30 | 31 | // Camera 32 | const camera = new PerspectiveCamera({ 33 | fov: 45 34 | }); 35 | 36 | camera.position.set(2, 2, 2); 37 | camera.lookAt(); 38 | 39 | // Helpers 40 | const controls = new OrbitControls(camera, renderer.canvas); 41 | controls.update(); 42 | 43 | const ambientLight = new Lights([ 44 | new AmbientLight({ 45 | intensity: { 46 | type: 'f', 47 | value: 0.7 48 | } 49 | }) 50 | ]); 51 | 52 | const directionalLights = new Lights([ 53 | new DirectionalLight({ 54 | intensity: { 55 | type: 'f', 56 | value: 0.7 57 | }, 58 | color: { 59 | type: '3f', 60 | value: new Color(0xffffff).v 61 | } 62 | }) 63 | ]); 64 | 65 | const pointLights = new Lights([ 66 | new PointLight({ 67 | intensity: { 68 | type: 'f', 69 | value: 0.7 70 | } 71 | }), 72 | new PointLight({ 73 | intensity: { 74 | type: 'f', 75 | value: 0.7 76 | } 77 | }), 78 | new PointLight({ 79 | intensity: { 80 | type: 'f', 81 | value: 0.7 82 | } 83 | }) 84 | ]); 85 | 86 | directionalLights.get()[0].position.set(1, 1, 1); 87 | 88 | scene.ambientLight = ambientLight; 89 | scene.directionalLights = directionalLights; 90 | scene.pointLights = pointLights; 91 | 92 | // Obj 93 | new ObjLoader('assets/models/obj/mass.obj') 94 | .then(data => { 95 | const geometry = new Geometry(data.vertices, data.indices, data.normals); 96 | 97 | const material = new Material({ 98 | type: 'phong', 99 | ambientLight, 100 | directionalLights, 101 | pointLights, 102 | uniforms: { 103 | uDiffuse: { 104 | type: '3f', 105 | value: new Color(0xff0000).v 106 | } 107 | } 108 | }); 109 | 110 | const mesh = new Mesh(geometry, material); 111 | 112 | const scale = 0.25; 113 | mesh.scale.set(scale, scale, scale); 114 | scene.add(mesh); 115 | }) 116 | .catch(error => { 117 | console.log('error loading', error); // eslint-disable-line no-console 118 | }); 119 | 120 | function resize() { 121 | const width = window.innerWidth; 122 | const height = window.innerHeight; 123 | renderer.setSize(width, height); 124 | camera.aspect = width / height; 125 | camera.updateProjectionMatrix(); 126 | } 127 | resize(); 128 | 129 | window.addEventListener('resize', resize); 130 | 131 | function update(time) { 132 | requestAnimationFrame(update); 133 | 134 | stats.begin(); 135 | 136 | camera.updateMatrixWorld(); 137 | 138 | const radius = 20; 139 | const t = time * 0.0005; 140 | 141 | pointLights.get().forEach((light, i) => { 142 | const theta = i / pointLights.length * Math.PI * 2; 143 | const x = Math.cos(t + theta) * radius; 144 | const y = Math.cos(t + theta) * radius; 145 | const z = Math.sin(t + theta) * radius; 146 | light.position.set(x, y, z); 147 | }); 148 | 149 | renderer.render(scene, camera); 150 | 151 | stats.end(); 152 | } 153 | update(); 154 | -------------------------------------------------------------------------------- /examples/src/js/pbr/shader.glsl.js: -------------------------------------------------------------------------------- 1 | import { ShaderChunks } from '../../../../src/index.ts'; 2 | 3 | export const hookFragmentPre = ` 4 | in vec3 vReflect; 5 | in vec4 vWorldPosition; 6 | uniform float uGamma; 7 | uniform float uExposure; 8 | uniform float uMetalness; 9 | uniform float uRoughness; 10 | uniform sampler2D uAlbedioMap; 11 | uniform sampler2D uNormalMap; 12 | uniform sampler2D uAoMap; 13 | uniform vec2 uNormalScale; 14 | uniform sampler2D uMetalnessMap; 15 | uniform samplerCube uEnvironment; 16 | 17 | ${ShaderChunks.EnvMapCube} 18 | ${ShaderChunks.Tonemap.tonemapReinhard} 19 | ${ShaderChunks.Gamma} 20 | 21 | // Per-Pixel Tangent Space Normal Mapping 22 | // http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html 23 | vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) { 24 | 25 | vec3 q0 = dFdx( eye_pos.xyz ); 26 | vec3 q1 = dFdy( eye_pos.xyz ); 27 | vec2 st0 = dFdx( vUv.st ); 28 | vec2 st1 = dFdy( vUv.st ); 29 | 30 | vec3 S = normalize( q0 * st1.t - q1 * st0.t ); 31 | vec3 T = normalize( -q0 * st1.s + q1 * st0.s ); 32 | vec3 N = normalize( surf_norm ); 33 | 34 | vec3 mapN = texture( uNormalMap, vUv ).xyz * 2.0 - 1.0; 35 | mapN.xy = uNormalScale * mapN.xy; 36 | mat3 tsn = mat3( S, T, N ); 37 | return normalize( tsn * mapN ); 38 | } 39 | `; 40 | 41 | export const hookFragmentMain = ` 42 | 43 | // In no way is this physically correct... 44 | // With the right creative approach you probably don't need all that complex math (: 45 | 46 | // Albedo 47 | color = texture(uAlbedioMap, vUv).rgb; 48 | 49 | // Get normal 50 | normal = perturbNormal2Arb(-vWorldPosition.xyz, vNormal); 51 | 52 | // Env map 53 | vec3 texelEnvMap = mix(vec3(0.0), texture(uEnvironment, envMapCube(vReflect)).rgb, uMetalness); 54 | 55 | // Metalness 56 | vec3 texelMetalnessMap = texture(uMetalnessMap, vUv).rgb; 57 | 58 | texelEnvMap *= texelMetalnessMap; 59 | 60 | color *= mix(color, texelEnvMap, (color.r + color.g + color.b) / 3.0); 61 | 62 | // Exposure 63 | color *= uExposure; 64 | 65 | // white balance 66 | color = tonemapReinhard(color); 67 | 68 | // gamma correction 69 | color = toGamma(color); 70 | `; 71 | -------------------------------------------------------------------------------- /examples/src/js/points/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | GL, 3 | Renderer, 4 | Scene, 5 | PerspectiveCamera, 6 | GridHelper, 7 | OrbitControls, 8 | AxisHelper, 9 | Geometry, 10 | MathUtils, 11 | Mesh, 12 | Material, 13 | Constants 14 | } from '../../../../src/index.ts'; 15 | 16 | const { guiController } = require('../gui')(); 17 | 18 | // Renderer 19 | const renderer = new Renderer({ 20 | ratio: window.innerWidth / window.innerHeight, 21 | prefferedContext: guiController.context 22 | }); 23 | renderer.setDevicePixelRatio(window.devicePixelRatio); 24 | document.body.appendChild(renderer.canvas); 25 | 26 | // Scene 27 | const scene = new Scene(); 28 | 29 | // Camera 30 | const camera = new PerspectiveCamera({ 31 | fov: 45, 32 | far: 500 33 | }); 34 | 35 | camera.position.set(10, 5, 10); 36 | camera.lookAt(); 37 | 38 | // Helpers 39 | const controls = new OrbitControls(camera, renderer.canvas); 40 | 41 | const grid = new GridHelper(10); 42 | scene.add(grid); 43 | 44 | const axis = new AxisHelper(1); 45 | scene.add(axis); 46 | 47 | // Objects 48 | const TOTAL_POINTS = 200; 49 | const bufferVertices = new Float32Array(TOTAL_POINTS * 3); 50 | const range = 3; 51 | 52 | let i3 = 0; 53 | for (let i = 0; i < TOTAL_POINTS; i += 1) { 54 | i3 = i * 3; 55 | bufferVertices[i3] = MathUtils.lerp(-range, range, Math.random()); 56 | bufferVertices[i3 + 1] = MathUtils.lerp(-range, range, Math.random()); 57 | bufferVertices[i3 + 2] = MathUtils.lerp(-range, range, Math.random()); 58 | } 59 | 60 | const geometry = new Geometry(bufferVertices); 61 | 62 | const hookVertexEndEs300 = ` 63 | vec4 mvPosition = uProjectionView.projectionMatrix * uModelViewMatrix * vec4(aVertexPosition + transformed, 1.0); 64 | gl_PointSize = uSize * (100.0 / length(mvPosition.xyz)); 65 | gl_Position = mvPosition; 66 | `; 67 | 68 | const hookVertexEndEs100 = ` 69 | vec4 mvPosition = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition + transformed, 1.0); 70 | gl_PointSize = uSize * (100.0 / length(mvPosition.xyz)); 71 | gl_Position = mvPosition; 72 | `; 73 | 74 | const hookFragmentEnd = ` 75 | if(length(gl_PointCoord - 0.5) > 0.5) { 76 | discard; 77 | } 78 | outgoingColor = vec4(1.0); 79 | `; 80 | 81 | const shader = new Material({ 82 | hookVertexPre: ` 83 | uniform float uSize; 84 | `, 85 | hookVertexEnd: GL.webgl2 ? hookVertexEndEs300 : hookVertexEndEs100, 86 | hookFragmentEnd, 87 | uniforms: { 88 | uSize: { 89 | type: 'f', 90 | value: 0.5 91 | } 92 | }, 93 | drawType: Constants.DRAW_POINTS 94 | }); 95 | 96 | const mesh = new Mesh(geometry, shader); 97 | 98 | scene.add(mesh); 99 | 100 | controls.update(); 101 | 102 | function resize() { 103 | const width = window.innerWidth; 104 | const height = window.innerHeight; 105 | renderer.setSize(width, height); 106 | camera.aspect = width / height; 107 | camera.updateProjectionMatrix(); 108 | } 109 | resize(); 110 | 111 | window.addEventListener('resize', resize); 112 | 113 | function update() { 114 | requestAnimationFrame(update); 115 | camera.updateMatrixWorld(); 116 | renderer.render(scene, camera); 117 | } 118 | update(); 119 | -------------------------------------------------------------------------------- /examples/src/js/raycast/index.js: -------------------------------------------------------------------------------- 1 | import { vec2 } from 'gl-matrix'; 2 | import { 3 | Renderer, 4 | Scene, 5 | PerspectiveCamera, 6 | Mesh, 7 | Material, 8 | GridHelper, 9 | OrbitControls, 10 | AxisHelper, 11 | Color, 12 | DirectionalLight, 13 | RayCaster, 14 | Vector2, 15 | SphereGeometry, 16 | BoxGeometry, 17 | Lights 18 | } from '../../../../src/index.ts'; 19 | import stats from '../stats'; 20 | 21 | const { guiController } = require('../gui')(); 22 | 23 | // Renderer 24 | const renderer = new Renderer({ 25 | ratio: window.innerWidth / window.innerHeight, 26 | prefferedContext: guiController.context 27 | }); 28 | renderer.setDevicePixelRatio(window.devicePixelRatio); 29 | document.body.appendChild(renderer.canvas); 30 | 31 | // Scene 32 | const scene = new Scene(); 33 | 34 | // Camera 35 | const camera = new PerspectiveCamera({ 36 | fov: 45 37 | }); 38 | 39 | camera.position.set(15, 10, 15); 40 | camera.lookAt(); 41 | 42 | const directionalLights = new Lights([ 43 | new DirectionalLight({ 44 | intensity: { 45 | type: 'f', 46 | value: 0.7 47 | } 48 | }) 49 | ]); 50 | 51 | directionalLights.get()[0].position.set(0.75, 1, 0.75); 52 | 53 | scene.directionalLights = directionalLights; 54 | 55 | const geometry = new BoxGeometry(2, 2, 2); 56 | const material = new Material({ 57 | type: 'lambert', 58 | name: 'Box', 59 | hookFragmentPre: ` 60 | uniform vec2 uIntersect; 61 | uniform float uVisible; 62 | `, 63 | hookFragmentMain: ` 64 | float circleRadius = 0.25; 65 | vec2 circleCenter = uIntersect; 66 | vec2 uvP = vec2(vUv.x, vUv.y); 67 | 68 | uvP -= circleCenter; 69 | 70 | float dist = sqrt(dot(uvP, uvP)); 71 | 72 | color *= 0.5; 73 | 74 | if (dist < circleRadius) { 75 | color = vec3(1.0); 76 | } 77 | 78 | color *= uVisible; 79 | `, 80 | hookFragmentEnd: ` 81 | outgoingColor = vec4(color, 1.0); 82 | `, 83 | uniforms: { 84 | uDiffuse: { 85 | type: '3f', 86 | value: new Color(0xff2a9d).v 87 | }, 88 | uVisible: { 89 | type: 'f', 90 | value: 0 91 | }, 92 | uIntersect: { 93 | type: '2f', 94 | value: new Vector2().v 95 | } 96 | }, 97 | directionalLights 98 | }); 99 | const box = new Mesh(geometry, material); 100 | 101 | const intersectHelper = new Mesh( 102 | new SphereGeometry(0.2), 103 | new Material({ 104 | uniforms: { 105 | uDiffuse: { 106 | type: '3f', 107 | value: new Color(0x000000).v 108 | } 109 | } 110 | }) 111 | ); 112 | 113 | box.position.y = 3; 114 | 115 | scene.add(box); 116 | scene.add(intersectHelper); 117 | 118 | // Ray in model space 119 | const raycaster = new RayCaster(); 120 | 121 | // Helpers 122 | const controls = new OrbitControls(camera, renderer.canvas); 123 | 124 | const grid = new GridHelper(10); 125 | scene.add(grid); 126 | 127 | const axis = new AxisHelper(1); 128 | scene.add(axis); 129 | 130 | controls.update(); 131 | 132 | function resize() { 133 | const width = window.innerWidth; 134 | const height = window.innerHeight; 135 | renderer.setSize(width, height); 136 | camera.aspect = width / height; 137 | camera.updateProjectionMatrix(); 138 | } 139 | resize(); 140 | 141 | const mouse = new Vector2(); 142 | 143 | function onMouseMove(event) { 144 | mouse.x = event.clientX / window.innerWidth * 2 - 1; 145 | mouse.y = -(event.pageY / window.innerHeight * 2 - 1); 146 | } 147 | 148 | window.addEventListener('resize', resize); 149 | window.addEventListener('mousemove', onMouseMove); 150 | 151 | function update() { 152 | requestAnimationFrame(update); 153 | 154 | stats.begin(); 155 | 156 | camera.updateMatrixWorld(); 157 | 158 | raycaster.setFromCamera(mouse, scene, camera, box); 159 | const intersect = raycaster.intersectObject(box); 160 | 161 | if (intersect) { 162 | intersectHelper.position.copy(intersect.point); 163 | intersectHelper.visible = true; 164 | box.material.uniforms.uVisible.value = 1; 165 | vec2.copy(box.material.uniforms.uIntersect.value, intersect.uv.v); 166 | } else { 167 | intersectHelper.visible = false; 168 | box.material.uniforms.uVisible.value = 0; 169 | } 170 | 171 | box.rotation.x += 0.01; 172 | box.rotation.y += 0.01; 173 | box.rotation.z += 0.01; 174 | 175 | renderer.render(scene, camera); 176 | 177 | stats.end(); 178 | } 179 | update(); 180 | -------------------------------------------------------------------------------- /examples/src/js/rendertarget/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | GridHelper, 6 | OrbitControls, 7 | AxisHelper, 8 | RenderTarget, 9 | Material, 10 | Mesh, 11 | PlaneGeometry, 12 | OrthographicCamera, 13 | ShaderChunks, 14 | Clock 15 | } from '../../../../src/index.ts'; 16 | import stats from '../stats'; 17 | 18 | const { guiController } = require('../gui')(); 19 | 20 | // Renderer 21 | const renderer = new Renderer({ 22 | ratio: window.innerWidth / window.innerHeight, 23 | prefferedContext: guiController.context 24 | }); 25 | renderer.setDevicePixelRatio(window.devicePixelRatio); 26 | document.body.appendChild(renderer.canvas); 27 | 28 | // Render target 29 | // const renderTarget = new RenderTarget({ 30 | // width: window.innerWidth, 31 | // height: window.innerHeight, 32 | // pixelRatio: renderer.pixelRatio 33 | // }); 34 | const renderTarget = new RenderTarget({ 35 | width: 1024, 36 | height: 1024, 37 | pixelRatio: renderer.pixelRatio 38 | }); 39 | 40 | // Scene 41 | const scene = new Scene(); 42 | const scene2 = new Scene(); 43 | 44 | // Camera 45 | const cameras = { 46 | main: new PerspectiveCamera({ 47 | fov: 45 48 | }), 49 | rtt: new OrthographicCamera({ 50 | fov: 45 51 | }) 52 | }; 53 | 54 | cameras.main.position.set(10, 5, 10); 55 | cameras.main.lookAt(); 56 | 57 | cameras.rtt.position.set(0, 0, 1); 58 | cameras.rtt.lookAt(0, 0, 0); 59 | 60 | // Helpers 61 | const controls = new OrbitControls(cameras.main, renderer.canvas); 62 | 63 | const grid = new GridHelper(10); 64 | scene.add(grid); 65 | 66 | const axis = new AxisHelper(1); 67 | scene.add(axis); 68 | 69 | controls.update(); 70 | 71 | const clock = new Clock(true); 72 | 73 | const material = new Material({ 74 | uniforms: { 75 | uTexture0: { 76 | type: 't', 77 | value: renderTarget.texture 78 | }, 79 | uTime: { 80 | type: 'f', 81 | value: 0 82 | } 83 | }, 84 | fragmentShader: `${ShaderChunks.EsVersion} 85 | #HOOK_PRECISION 86 | in vec2 vUv; 87 | uniform sampler2D uTexture0; 88 | uniform float uTime; 89 | out vec4 outgoingColor; 90 | 91 | void main(void) { 92 | vec3 color = texture(uTexture0, vUv).rgb; 93 | float r = sin(color.r + uTime) * 0.5 + 0.5; 94 | float g = cos(color.g + uTime) * 0.5 + 0.5; 95 | float b = sin(color.b + uTime) * 0.5 + 0.5; 96 | outgoingColor = vec4(r, g, b, 1.0); 97 | } 98 | ` 99 | }); 100 | 101 | const plane = new Mesh(new PlaneGeometry(2, 2), material); 102 | scene2.add(plane); 103 | 104 | function resize() { 105 | const width = window.innerWidth; 106 | const height = window.innerHeight; 107 | renderTarget.setSize(width, height); 108 | renderer.setSize(width, height); 109 | cameras.main.aspect = width / height; 110 | cameras.main.updateProjectionMatrix(); 111 | cameras.rtt.aspect = width / height; 112 | cameras.rtt.updateProjectionMatrix(); 113 | } 114 | resize(); 115 | 116 | window.addEventListener('resize', resize); 117 | 118 | let delta; 119 | function update() { 120 | requestAnimationFrame(update); 121 | 122 | stats.begin(); 123 | 124 | cameras.main.updateMatrixWorld(); 125 | cameras.rtt.updateMatrixWorld(); 126 | 127 | delta = clock.getDelta(); 128 | 129 | plane.material.uniforms.uTime.value += delta; 130 | 131 | renderTarget.render(scene, cameras.main); 132 | renderer.render(scene2, cameras.rtt); 133 | 134 | stats.end(); 135 | } 136 | update(); 137 | -------------------------------------------------------------------------------- /examples/src/js/scenegraph/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | AxisHelper, 6 | GridHelper, 7 | OrbitControls, 8 | SphereGeometry, 9 | Material, 10 | Mesh, 11 | Color, 12 | DirectionalLight, 13 | Object3D, 14 | BoxGeometry, 15 | Lights, 16 | Constants 17 | } from '../../../../src/index.ts'; 18 | import stats from '../stats'; 19 | 20 | const { gui, guiController } = require('../gui')(); 21 | 22 | // Renderer 23 | const renderer = new Renderer({ 24 | ratio: window.innerWidth / window.innerHeight, 25 | prefferedContext: guiController.context 26 | }); 27 | renderer.setDevicePixelRatio(window.devicePixelRatio); 28 | document.body.appendChild(renderer.canvas); 29 | 30 | // Scene 31 | const scene = new Scene(); 32 | 33 | // Camera 34 | const camera = new PerspectiveCamera({ 35 | fov: 45 36 | }); 37 | 38 | camera.position.set(10, 5, 10); 39 | camera.lookAt(); 40 | 41 | // Helpers 42 | const controls = new OrbitControls(camera, renderer.canvas); 43 | const grid = new GridHelper(10); 44 | scene.add(grid); 45 | const axis = new AxisHelper(1); 46 | scene.add(axis); 47 | controls.update(); 48 | 49 | const directionalLights = new Lights([ 50 | new DirectionalLight({ 51 | intensity: { 52 | type: 'f', 53 | value: 0.7 54 | } 55 | }) 56 | ]); 57 | 58 | directionalLights.get()[0].position.set(1, 1, 1); 59 | 60 | scene.directionalLights = directionalLights; 61 | 62 | const container = new Object3D(); 63 | 64 | const material = new Material({ 65 | type: 'lambert', 66 | uniforms: { 67 | uDiffuse: { 68 | type: '3f', 69 | value: new Color(0x666666).v 70 | } 71 | }, 72 | directionalLights 73 | }); 74 | 75 | const material2 = new Material({ 76 | drawType: Constants.DRAW_LINES, 77 | uniforms: { 78 | uDiffuse: { 79 | type: '3f', 80 | value: new Color(0xff0000).v 81 | } 82 | } 83 | }); 84 | 85 | const geometry0 = new BoxGeometry(2, 2, 2); 86 | const mesh0 = new Mesh(geometry0, material); 87 | mesh0.setParent(container); 88 | 89 | const geometry1 = new SphereGeometry(2, 32, 32); 90 | const mesh1 = new Mesh(geometry1, material2); 91 | mesh1.setParent(mesh0); 92 | 93 | mesh0.position.x = -10; 94 | mesh1.position.x = 10; 95 | 96 | scene.add(mesh0); 97 | scene.add(mesh1); 98 | 99 | gui.add(mesh0.position, 'x', -10, 10); 100 | gui.add(mesh0.position, 'y', -10, 10); 101 | gui.add(mesh0.position, 'z', -10, 10); 102 | gui.add(mesh1.position, 'x', -10, 10); 103 | 104 | function resize() { 105 | const width = window.innerWidth; 106 | const height = window.innerHeight; 107 | renderer.setSize(width, height); 108 | camera.aspect = width / height; 109 | camera.updateProjectionMatrix(); 110 | } 111 | resize(); 112 | 113 | window.addEventListener('resize', resize); 114 | 115 | function update() { 116 | requestAnimationFrame(update); 117 | 118 | stats.begin(); 119 | 120 | camera.updateMatrixWorld(); 121 | 122 | container.rotation.x += 0.01; 123 | container.rotation.y += 0.01; 124 | container.rotation.z += 0.01; 125 | mesh0.rotation.x += 0.02; 126 | mesh0.rotation.y -= 0.02; 127 | mesh0.rotation.z += 0.02; 128 | 129 | // Container doesn't get drawn so updateMatrix() needs 130 | // to be called manually 131 | container.updateMatrix(camera); 132 | renderer.render(scene, camera); 133 | 134 | stats.end(); 135 | } 136 | update(); 137 | -------------------------------------------------------------------------------- /examples/src/js/shadows/ShadowMapRenderer.js: -------------------------------------------------------------------------------- 1 | import { mat4, vec3 } from 'gl-matrix'; 2 | import { PerspectiveCamera, RenderTarget } from '../../../../src/index.ts'; 3 | 4 | const UP = vec3.fromValues(0, 1, 0); 5 | 6 | export default class ShadowMapRenderer { 7 | constructor(light) { 8 | this.light = light; 9 | this.camera = new PerspectiveCamera({ 10 | near: 0.01, 11 | far: 50, 12 | fov: 70 13 | }); 14 | this.lightViewMatrix = mat4.create(); 15 | this.center = vec3.fromValues(0, 0, 0); 16 | mat4.translate( 17 | this.lightViewMatrix, 18 | this.lightViewMatrix, 19 | this.light.position.v 20 | ); 21 | mat4.lookAt(this.lightViewMatrix, this.light.position.v, this.center, UP); 22 | 23 | // Render target 24 | this.renderTarget = new RenderTarget({ 25 | width: 1024, 26 | height: 1024 27 | }); 28 | } 29 | 30 | resize(width, height) { 31 | this.camera.aspect = width / height; 32 | this.camera.updateProjectionMatrix(); 33 | } 34 | 35 | render(scene) { 36 | mat4.identity(this.lightViewMatrix); 37 | mat4.translate( 38 | this.lightViewMatrix, 39 | this.lightViewMatrix, 40 | this.light.position.v 41 | ); 42 | mat4.lookAt(this.lightViewMatrix, this.light.position.v, this.center, UP); 43 | this.renderTarget.render(scene, this.camera); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/src/js/stats.js: -------------------------------------------------------------------------------- 1 | import Stats from 'stats-js'; // eslint-disable-line import/no-extraneous-dependencies 2 | 3 | const stats = new Stats(); 4 | stats.domElement.style.position = 'absolute'; 5 | stats.domElement.style.left = '0'; 6 | stats.domElement.style.top = '0'; 7 | 8 | document.body.appendChild(stats.domElement); 9 | 10 | export default stats; 11 | -------------------------------------------------------------------------------- /examples/src/js/stereo/StereoRender.js: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import { Renderer, GL, UniformBuffers } from '../../../../src/index.ts'; 3 | 4 | let gl; 5 | 6 | export default class StereoRender extends Renderer { 7 | drawObjects = (scene, camera) => { 8 | if (gl instanceof window.WebGL2RenderingContext) { 9 | // Update global uniform buffers 10 | UniformBuffers.updateProjectionView(gl, camera.projectionMatrix); 11 | } 12 | 13 | // Render the scene objects 14 | scene.objects.forEach(child => { 15 | if (child.isInstanced) { 16 | child.drawInstance( 17 | scene.modelViewMatrix, 18 | camera.projectionMatrix, 19 | camera 20 | ); 21 | } else { 22 | child.draw(scene.modelViewMatrix, camera.projectionMatrix, camera); 23 | } 24 | }); 25 | }; 26 | 27 | render( 28 | scene, 29 | leftProjectionMatrix, 30 | leftViewMatrix, 31 | rightProjectionMatrix, 32 | rightViewMatrix, 33 | cameraL, 34 | cameraR 35 | ) { 36 | gl = GL.get(); 37 | 38 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 39 | 40 | // Draw both eyes 41 | mat4.identity(scene.modelViewMatrix); 42 | 43 | // Update the scene 44 | scene.update(); 45 | 46 | // Left 47 | gl.viewport(0.0, 0.0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight); 48 | 49 | mat4.lookAt( 50 | leftViewMatrix, 51 | cameraL.position.v, 52 | cameraL.target.v, 53 | cameraL.up.v 54 | ); 55 | 56 | this.drawObjects(scene, cameraL, leftViewMatrix); 57 | 58 | // Right 59 | gl.viewport( 60 | gl.drawingBufferWidth * 0.5, 61 | 0, 62 | gl.drawingBufferWidth * 0.5, 63 | gl.drawingBufferHeight 64 | ); 65 | 66 | mat4.lookAt( 67 | rightViewMatrix, 68 | cameraR.position.v, 69 | cameraR.target.v, 70 | cameraR.up.v 71 | ); 72 | 73 | this.drawObjects(scene, cameraR, rightViewMatrix); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/src/js/template/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | GridHelper, 6 | OrbitControls, 7 | AxisHelper, 8 | CameraHelper 9 | } from '../../../../src/index.ts'; 10 | import stats from '../stats'; 11 | 12 | const { gui, guiController } = require('../gui')(); 13 | 14 | guiController.debug = true; 15 | gui.add(guiController, 'debug'); 16 | 17 | // Renderer 18 | const renderer = new Renderer({ 19 | ratio: window.innerWidth / window.innerHeight, 20 | prefferedContext: guiController.context 21 | }); 22 | renderer.setDevicePixelRatio(window.devicePixelRatio); 23 | renderer.setSissorTest(true); 24 | document.body.appendChild(renderer.canvas); 25 | 26 | // Scene 27 | const scene = new Scene(); 28 | 29 | // Camera 30 | const cameras = { 31 | dev: new PerspectiveCamera({ 32 | fov: 45, 33 | far: 500, 34 | ratio: window.innerWidth / window.innerHeight 35 | }), 36 | main: new PerspectiveCamera({ 37 | fov: 45, 38 | far: 500, 39 | ratio: window.innerWidth / window.innerHeight 40 | }) 41 | }; 42 | 43 | cameras.dev.position.set(10, 5, 10); 44 | cameras.dev.lookAt(); 45 | 46 | cameras.main.position.set(10, 5, 10); 47 | cameras.main.lookAt(); 48 | 49 | // Helpers 50 | const controls = new OrbitControls(cameras.dev, renderer.canvas); 51 | controls.smoothing = true; 52 | 53 | const grid = new GridHelper(10); 54 | scene.add(grid); 55 | 56 | const axis = new AxisHelper(1); 57 | scene.add(axis); 58 | 59 | const cameraHelper = new CameraHelper(cameras.main); 60 | scene.add(cameraHelper); 61 | 62 | controls.update(); 63 | 64 | function resize() { 65 | const width = window.innerWidth; 66 | const height = window.innerHeight; 67 | renderer.setSize(width, height); 68 | cameras.dev.aspect = width / height; 69 | cameras.dev.updateProjectionMatrix(); 70 | cameras.main.aspect = width / height; 71 | cameras.main.updateProjectionMatrix(); 72 | } 73 | resize(); 74 | 75 | window.addEventListener('resize', resize); 76 | 77 | function render(camera, x, y, width, height) { 78 | renderer.setSissor( 79 | x, 80 | y, 81 | width * window.innerWidth, 82 | height * window.innerHeight 83 | ); 84 | renderer.setViewport( 85 | x, 86 | y, 87 | width * window.innerWidth, 88 | height * window.innerHeight 89 | ); 90 | renderer.render(scene, camera); 91 | } 92 | 93 | function update() { 94 | requestAnimationFrame(update); 95 | 96 | stats.begin(); 97 | 98 | cameraHelper.update(); 99 | controls.update(); 100 | 101 | cameras.dev.updateMatrixWorld(); 102 | cameras.main.updateMatrixWorld(); 103 | 104 | if (guiController.debug) { 105 | render(cameras.dev, 0, 0, 1, 1); 106 | render(cameras.main, 0, 0, 0.25, 0.25); 107 | } else { 108 | render(cameras.main, 0, 0, 1, 1); 109 | } 110 | 111 | stats.end(); 112 | } 113 | update(); 114 | -------------------------------------------------------------------------------- /examples/src/js/texture/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | Mesh, 6 | Material, 7 | PlaneGeometry, 8 | GridHelper, 9 | OrbitControls, 10 | AxisHelper, 11 | Texture 12 | } from '../../../../src/index.ts'; 13 | import stats from '../stats'; 14 | 15 | const { gui, guiController } = require('../gui')(); 16 | 17 | // Renderer 18 | const renderer = new Renderer({ 19 | ratio: window.innerWidth / window.innerHeight, 20 | prefferedContext: guiController.context 21 | }); 22 | renderer.setDevicePixelRatio(window.devicePixelRatio); 23 | document.body.appendChild(renderer.canvas); 24 | 25 | // Scene 26 | const scene = new Scene(); 27 | 28 | // Camera 29 | const camera = new PerspectiveCamera({ 30 | fov: 45 31 | }); 32 | 33 | camera.position.set(10, 5, 10); 34 | camera.lookAt(); 35 | 36 | const texture0 = new Texture({ 37 | src: 'assets/textures/texture-nopow2.jpg' 38 | }); 39 | 40 | const texture1 = new Texture({ 41 | src: 'assets/textures/texture-nopow2-2.jpg' 42 | }); 43 | 44 | const geometry = new PlaneGeometry(1, 1, 1, 1, 'XY'); 45 | const material = new Material({ 46 | name: 'Plane', 47 | hookFragmentPre: ` 48 | uniform sampler2D uTexture0; 49 | uniform sampler2D uTexture1; 50 | uniform float uMix; 51 | `, 52 | hookFragmentMain: ` 53 | color = mix(texture(uTexture0, vUv).rgb, texture(uTexture1, vUv).rgb, uMix); 54 | `, 55 | uniforms: { 56 | uMix: { 57 | type: 'f', 58 | value: 1 59 | }, 60 | uTexture0: { 61 | type: 't', 62 | value: texture0.texture 63 | }, 64 | uTexture1: { 65 | type: 't', 66 | value: texture1.texture 67 | } 68 | } 69 | }); 70 | 71 | const plane = new Mesh(geometry, material); 72 | scene.add(plane); 73 | 74 | gui.add(plane.material.uniforms.uMix, 'value', 0, 1); 75 | 76 | // Helpers 77 | const controls = new OrbitControls(camera, renderer.canvas); 78 | 79 | const grid = new GridHelper(10); 80 | scene.add(grid); 81 | // 82 | const axis = new AxisHelper(1); 83 | scene.add(axis); 84 | 85 | controls.update(); 86 | 87 | function resize() { 88 | const width = window.innerWidth; 89 | const height = window.innerHeight; 90 | renderer.setSize(width, height); 91 | camera.aspect = width / height; 92 | camera.updateProjectionMatrix(); 93 | } 94 | resize(); 95 | 96 | window.addEventListener('resize', resize); 97 | 98 | function update() { 99 | requestAnimationFrame(update); 100 | 101 | stats.begin(); 102 | 103 | camera.updateMatrixWorld(); 104 | renderer.render(scene, camera); 105 | 106 | stats.end(); 107 | } 108 | update(); 109 | -------------------------------------------------------------------------------- /examples/src/js/texture3d_advanced/distance-functions.js: -------------------------------------------------------------------------------- 1 | import { vec2, vec3 } from 'gl-matrix'; 2 | // http://iquilezles.org/www/articles/distfunctions/distfunctions.htm 3 | 4 | /** 5 | float sdSphere( vec3 p, float s ) 6 | { 7 | return length(p)-s; 8 | } 9 | */ 10 | export function sphere(p, s) { 11 | return vec3.length(p) - s; 12 | } 13 | 14 | /** 15 | float sdTorus( vec3 p, vec2 t ) 16 | { 17 | vec2 q = vec2(length(p.xz)-t.x,p.y); 18 | return length(q)-t.y; 19 | } 20 | */ 21 | 22 | export function torus(p, t) { 23 | let lengthPxz; 24 | const q = vec2.create(); 25 | return ((p_, t_) => { 26 | lengthPxz = vec2.length([p_[0], p_[2]]); 27 | vec2.set(q, lengthPxz - t_[0], p_[1]); 28 | return vec2.length(q) - t_[1]; 29 | })(p, t); 30 | } 31 | -------------------------------------------------------------------------------- /examples/src/js/texturecube/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | OrbitControls, 6 | TextureCube, 7 | Material, 8 | Mesh, 9 | JsonLoader, 10 | Geometry 11 | } from '../../../../src/index.ts'; 12 | import stats from '../stats'; 13 | 14 | const { guiController } = require('../gui')(); 15 | 16 | // Renderer 17 | const renderer = new Renderer({ 18 | ratio: window.innerWidth / window.innerHeight, 19 | prefferedContext: guiController.context 20 | }); 21 | renderer.setDevicePixelRatio(window.devicePixelRatio); 22 | document.body.appendChild(renderer.canvas); 23 | 24 | // Scene 25 | const scene = new Scene(); 26 | 27 | // Camera 28 | const camera = new PerspectiveCamera({ 29 | fov: 45 30 | }); 31 | 32 | camera.position.set(10, 5, 10); 33 | camera.lookAt(); 34 | 35 | // Helpers 36 | const controls = new OrbitControls(camera, renderer.canvas); 37 | 38 | controls.update(); 39 | 40 | const texture = new TextureCube({ 41 | src: [ 42 | 'assets/textures/cube/blackice/px.jpg', 43 | 'assets/textures/cube/blackice/nx.jpg', 44 | 'assets/textures/cube/blackice/py.jpg', 45 | 'assets/textures/cube/blackice/ny.jpg', 46 | 'assets/textures/cube/blackice/pz.jpg', 47 | 'assets/textures/cube/blackice/nz.jpg' 48 | ] 49 | }); 50 | 51 | let mesh; 52 | 53 | new JsonLoader('assets/models/json/mass.json') 54 | .then(data => { 55 | const geometry = new Geometry(data.vertices, data.indices, data.normals); 56 | 57 | const material = new Material({ 58 | hookFragmentPre: ` 59 | uniform samplerCube uTexture0; 60 | `, 61 | hookFragmentMain: ` 62 | color = texture(uTexture0, vNormal).rgb; 63 | `, 64 | uniforms: { 65 | uTexture0: { 66 | type: 'tc', 67 | value: texture.texture 68 | } 69 | } 70 | }); 71 | 72 | mesh = new Mesh(geometry, material); 73 | 74 | const scale = 2.25; 75 | mesh.scale.set(scale, scale, scale); 76 | scene.add(mesh); 77 | }) 78 | .catch(error => { 79 | console.log('error loading', error); // eslint-disable-line no-console 80 | }); 81 | 82 | function resize() { 83 | const width = window.innerWidth; 84 | const height = window.innerHeight; 85 | renderer.setSize(width, height); 86 | camera.aspect = width / height; 87 | camera.updateProjectionMatrix(); 88 | } 89 | resize(); 90 | 91 | window.addEventListener('resize', resize); 92 | 93 | function update() { 94 | requestAnimationFrame(update); 95 | 96 | stats.begin(); 97 | 98 | camera.updateMatrixWorld(); 99 | 100 | if (mesh) { 101 | mesh.rotation.y += 0.003; 102 | } 103 | 104 | renderer.render(scene, camera); 105 | 106 | stats.end(); 107 | } 108 | update(); 109 | -------------------------------------------------------------------------------- /examples/src/js/texturevideo/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Renderer, 3 | Scene, 4 | PerspectiveCamera, 5 | Mesh, 6 | Material, 7 | BoxGeometry, 8 | GridHelper, 9 | OrbitControls, 10 | AxisHelper, 11 | TextureVideo 12 | } from '../../../../src/index.ts'; 13 | import stats from '../stats'; 14 | 15 | const { guiController } = require('../gui')(); 16 | 17 | // Renderer 18 | const renderer = new Renderer({ 19 | ratio: window.innerWidth / window.innerHeight, 20 | prefferedContext: guiController.context 21 | }); 22 | renderer.setDevicePixelRatio(window.devicePixelRatio); 23 | document.body.appendChild(renderer.canvas); 24 | 25 | // Scene 26 | const scene = new Scene(); 27 | 28 | // Camera 29 | const camera = new PerspectiveCamera({ 30 | fov: 45 31 | }); 32 | 33 | camera.position.set(10, 5, 10); 34 | camera.lookAt(); 35 | 36 | // Objects 37 | const textureVideo = new TextureVideo({ 38 | src: 'assets/textures/texture.mp4', 39 | loop: true 40 | }); 41 | 42 | textureVideo.once('canplaythrough', () => { 43 | console.log('canplaythrough'); // eslint-disable-line no-console 44 | }); 45 | 46 | textureVideo.on('ended', () => { 47 | console.log('ended'); // eslint-disable-line no-console 48 | }); 49 | 50 | const geometry = new BoxGeometry(1, 1, 1); 51 | const material = new Material({ 52 | name: 'Box', 53 | hookFragmentPre: ` 54 | uniform sampler2D uTexture0; 55 | `, 56 | hookFragmentMain: ` 57 | color = texture(uTexture0, vUv).rgb; 58 | `, 59 | uniforms: { 60 | uTexture0: { 61 | type: 't', 62 | value: textureVideo.texture 63 | } 64 | } 65 | }); 66 | const box = new Mesh(geometry, material); 67 | 68 | scene.add(box); 69 | 70 | // Helpers 71 | const controls = new OrbitControls(camera, renderer.canvas); 72 | 73 | const grid = new GridHelper(10); 74 | scene.add(grid); 75 | 76 | const axis = new AxisHelper(1); 77 | scene.add(axis); 78 | 79 | controls.update(); 80 | 81 | function resize() { 82 | const width = window.innerWidth; 83 | const height = window.innerHeight; 84 | renderer.setSize(width, height); 85 | camera.aspect = width / height; 86 | camera.updateProjectionMatrix(); 87 | } 88 | resize(); 89 | 90 | window.addEventListener('resize', resize); 91 | 92 | function update() { 93 | requestAnimationFrame(update); 94 | 95 | stats.begin(); 96 | 97 | camera.updateMatrixWorld(); 98 | box.rotation.x += 0.01; 99 | box.rotation.y += 0.01; 100 | textureVideo.update(); 101 | renderer.render(scene, camera); 102 | 103 | stats.end(); 104 | } 105 | update(); 106 | -------------------------------------------------------------------------------- /examples/src/js/transformfeedback/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | GL, 3 | Renderer, 4 | Scene, 5 | PerspectiveCamera, 6 | OrbitControls, 7 | MathUtils, 8 | Geometry, 9 | Material, 10 | Vao 11 | } from '../../../../src/index.ts'; 12 | import Shaders from './shaders.glsl'; 13 | import TFMesh from './TFMesh'; 14 | 15 | const { guiController } = require('../gui')(['webgl2']); 16 | 17 | // Renderer 18 | const renderer = new Renderer({ 19 | ratio: window.innerWidth / window.innerHeight, 20 | prefferedContext: guiController.context 21 | }); 22 | renderer.setDevicePixelRatio(window.devicePixelRatio); 23 | document.body.appendChild(renderer.canvas); 24 | 25 | // Scene 26 | const scene = new Scene(); 27 | 28 | // Camera 29 | const camera = new PerspectiveCamera({ 30 | fov: 45, 31 | far: 500 32 | }); 33 | 34 | camera.position.set(20, 10, 20); 35 | camera.lookAt(); 36 | 37 | // Helpers 38 | const controls = new OrbitControls(camera, renderer.canvas); 39 | controls.update(); 40 | 41 | // Objects 42 | const shaderEmit = new Material({ 43 | vertexShader: Shaders.emit.vertexShader, 44 | fragmentShader: Shaders.emit.fragmentShader 45 | }); 46 | 47 | const shaderDraw = new Material({ 48 | hookVertexPre: Shaders.draw.hookVertexPre, 49 | hookVertexEnd: Shaders.draw.hookVertexEnd, 50 | hookFragmentPre: Shaders.draw.hookFragmentPre, 51 | hookFragmentEnd: Shaders.draw.hookFragmentEnd, 52 | uniforms: { 53 | uSize: { 54 | type: 'f', 55 | value: 0.35 56 | } 57 | } 58 | }); 59 | 60 | const TOTAL_POINTS = 100000; 61 | const bufferVertices = new Float32Array(TOTAL_POINTS * 3); 62 | const bufferPositions = new Float32Array(TOTAL_POINTS * 3); 63 | const radius = 6; 64 | 65 | let i3 = 0; 66 | for (let i = 0; i < TOTAL_POINTS; i += 1) { 67 | i3 = i * 3; 68 | const position = MathUtils.randomSpherePoint(0, 0, 0, radius); 69 | bufferVertices[i3] = position[0]; 70 | bufferVertices[i3 + 1] = position[1]; 71 | bufferVertices[i3 + 2] = position[2]; 72 | 73 | bufferPositions[i3] = 0; 74 | // Offset the position in time 75 | bufferPositions[i3 + 1] = i * (Math.PI * 2 / TOTAL_POINTS); 76 | bufferPositions[i3 + 2] = 0; 77 | } 78 | 79 | const createBuffer = (gl, data) => { 80 | const buffer = gl.createBuffer(); 81 | gl.bindBuffer(gl.UNIFORM_BUFFER, buffer); 82 | gl.bufferData(gl.UNIFORM_BUFFER, new Float32Array(data), gl.STREAM_COPY); 83 | gl.bindBuffer(gl.UNIFORM_BUFFER, null); 84 | return buffer; 85 | }; 86 | 87 | const gl = GL.get(); 88 | 89 | const vaos = [new Vao(), new Vao()]; 90 | 91 | // More buffers could be added like: color, lifespan 92 | const vbos = [ 93 | [createBuffer(gl, bufferPositions)], 94 | [createBuffer(gl, bufferPositions)] 95 | ]; 96 | 97 | const geometry = new Geometry(bufferVertices); 98 | const mesh = new TFMesh( 99 | geometry, 100 | vaos, 101 | vbos, 102 | shaderEmit, 103 | shaderDraw, 104 | TOTAL_POINTS 105 | ); 106 | 107 | scene.add(mesh); 108 | 109 | function resize() { 110 | const width = window.innerWidth; 111 | const height = window.innerHeight; 112 | renderer.setSize(width, height); 113 | camera.aspect = width / height; 114 | camera.updateProjectionMatrix(); 115 | } 116 | resize(); 117 | 118 | window.addEventListener('resize', resize); 119 | 120 | function update() { 121 | requestAnimationFrame(update); 122 | camera.updateMatrixWorld(); 123 | renderer.render(scene, camera); 124 | } 125 | update(); 126 | -------------------------------------------------------------------------------- /examples/src/js/transformfeedback/shaders.glsl.js: -------------------------------------------------------------------------------- 1 | import { ShaderChunks } from '../../../../src/index.ts'; 2 | 3 | export default { 4 | emit: { 5 | vertexShader: `${ShaderChunks.EsVersion} 6 | #HOOK_DEFINES 7 | 8 | ${ShaderChunks.ProjectionView} 9 | uniform float uTime; 10 | in vec3 aPosition; 11 | out vec3 vPosition; 12 | 13 | void main() { 14 | vPosition += aPosition + vec3(0.0, 0.025, 0.0); 15 | } 16 | `, 17 | fragmentShader: `${ShaderChunks.EsVersion} 18 | #HOOK_PRECISION 19 | #HOOK_DEFINES 20 | 21 | out vec4 outgoingColor; 22 | 23 | void main() { 24 | outgoingColor = vec4(1.0); 25 | } 26 | ` 27 | }, 28 | draw: { 29 | hookVertexPre: ` 30 | in vec3 aPosition; 31 | uniform float uSize; 32 | out vec3 vColor; 33 | `, 34 | hookVertexEnd: ` 35 | vec3 scaledVertex = aVertexPosition + normalize(aVertexPosition) * vec3(2.0); 36 | vec3 normalLerp = mix(aVertexPosition, scaledVertex, sin(aPosition.y)); 37 | vColor = normalize(normalLerp) * 0.5 + 0.5; 38 | vec4 mvPosition = uModelViewMatrix * vec4(normalLerp, 1.0); 39 | gl_PointSize = uSize * (100.0 / length(mvPosition.xyz)); 40 | gl_Position = uProjectionView.projectionMatrix * uModelViewMatrix * vec4(normalLerp, 1.0); 41 | `, 42 | hookFragmentPre: ` 43 | uniform float uTime; 44 | in vec3 vColor; 45 | `, 46 | hookFragmentEnd: ` 47 | if(length(gl_PointCoord - 0.5) > 0.5) { 48 | discard; 49 | } 50 | outgoingColor = vec4(vColor, 1); 51 | ` 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /examples/src/templates/cameradolly.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/cameradolly.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/decals.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/decals.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/draco.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/draco.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/fullscreen.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/fullscreen.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/hdr.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/hdr.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/hdrcube.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/hdrcube.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/includes/_example.pug: -------------------------------------------------------------------------------- 1 | include _partials 2 | 3 | doctype html 4 | html 5 | +head 6 | body.example 7 | a.ixviii(href='https://www.ixviii.io', title="ixviii") 8 | block content 9 | block script 10 | +analytics 11 | -------------------------------------------------------------------------------- /examples/src/templates/includes/_layout.pug: -------------------------------------------------------------------------------- 1 | include _partials 2 | 3 | doctype html 4 | html 5 | +head 6 | body.index 7 | block content 8 | block script 9 | +analytics 10 | -------------------------------------------------------------------------------- /examples/src/templates/includes/_partials.pug: -------------------------------------------------------------------------------- 1 | mixin head 2 | head 3 | meta(charset='utf-8') 4 | title Medium 5 | meta(name="viewport" content="width=device-width, initial-scale=1") 6 | link(rel='stylesheet', href='assets/css/style.css') 7 | 8 | mixin analytics 9 | script. 10 | if (document.location.hostname.search('amelierosser.github.io') !== -1) { 11 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 12 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 13 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 14 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 15 | 16 | ga('create', 'UA-88847401-2', 'auto'); 17 | ga('send', 'pageview'); 18 | } 19 | -------------------------------------------------------------------------------- /examples/src/templates/index.pug: -------------------------------------------------------------------------------- 1 | extends includes/_layout 2 | 3 | block content 4 | header 5 | h1 Medium 6 | p.description #{locals.description} 7 | hr 8 | nav.nav 9 | for category, key in locals.examples 10 | .nav__item 11 | h3 #{key} 12 | ul 13 | for demo in category 14 | li 15 | a(href=`${demo.id}.html`) #{demo.title} 16 | 17 | .nav__item 18 | a.ixviii(href='https://www.ixviii.io', title="ixviii") 19 | -------------------------------------------------------------------------------- /examples/src/templates/instancing.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/instancing.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/lambert.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/lambert.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/linegeometry.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/linegeometry.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/memory.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/memory.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/obj.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/obj.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/pbr.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/pbr.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/phong.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/phong.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/points.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/points.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/raycast.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/raycast.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/rendertarget.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/rendertarget.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/sandbox.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/sandbox.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/scenegraph.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/scenegraph.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/shadows.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/shadows.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/stereo.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/stereo.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/template.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/template.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/texture.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/texture.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/texture3d.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/texture3d.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/texture3d_advanced.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/texture3d_advanced.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/texturecube.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/texturecube.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/textureproj.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/textureproj.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/textureproj_advanced.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/textureproj_advanced.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/texturevideo.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/texturevideo.js') 5 | -------------------------------------------------------------------------------- /examples/src/templates/transformfeedback.pug: -------------------------------------------------------------------------------- 1 | extends includes/_example 2 | 3 | block script 4 | script(src='js/transformfeedback.js') 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ixviii.medium", 3 | "version": "0.4.3", 4 | "description": "Progressive WebGL toolkit for art.", 5 | "main": "lib/ixviii.medium.js", 6 | "scripts": { 7 | "start": 8 | "concurrently 'npm run server' 'npm run examples:js' 'npm run examples:html'", 9 | "server": 10 | "live-server ./examples --quiet --port=3000 --watch='js/*,assets/**/*,*.html' --ignore='src'", 11 | "examples:js": 12 | "webpack --config webpack.config.examples.js --colors --watch", 13 | "examples:html": "node ./pug.config.js", 14 | "examples:build": 15 | "NODE_ENV=production webpack --config webpack.config.examples.js --colors; node ./pug.config.js", 16 | "build": 17 | "NODE_ENV=production npm run lint; webpack --config webpack.config.build.js --colors; NODE_ENV=production webpack --config webpack.config.build.js --colors", 18 | "prepublish": "npm run build", 19 | "formatting": 20 | "prettier --write --single-quote --print-width 80 './src/**/*.ts'", 21 | "formatting:examples": 22 | "prettier --write --single-quote --print-width 80 './examples/src/js/**/*.js'", 23 | "lint:src": "tslint --fix ./src/**/*.ts", 24 | "lint:examples": 25 | "eslint --fix --ext .js ./examples/src ./internals --cache", 26 | "lint": "npm run lint:src; npm run lint:examples;", 27 | "precommit": "npm run lint" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/amelierosser/medium.git" 32 | }, 33 | "author": 34 | "Amelie Rosser (https://www.ixviii.io)", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/amelierosser/medium/issues" 38 | }, 39 | "homepage": "https://github.com/amelierosser/medium", 40 | "devDependencies": { 41 | "@types/gl-matrix": "^2.3.0", 42 | "@types/node": "^10.5.2", 43 | "@types/webgl2": "^0.0.2", 44 | "babel-cli": "^6.26.0", 45 | "babel-eslint": "^8.0.0", 46 | "babel-loader": "^7.1.2", 47 | "babel-plugin-module-resolver": "^2.7.1", 48 | "babel-plugin-transform-class-properties": "^6.24.1", 49 | "babel-preset-env": "^1.6.0", 50 | "babel-preset-minify": "^0.2.0", 51 | "babili-webpack-plugin": "^0.1.2", 52 | "concurrently": "^3.5.0", 53 | "eslint": "^4.6.1", 54 | "eslint-config-airbnb": "^15.1.0", 55 | "eslint-config-airbnb-base": "^12.0.0", 56 | "eslint-config-prettier": "^2.4.0", 57 | "eslint-plugin-import": "^2.7.0", 58 | "eslint-plugin-prettier": "^2.2.0", 59 | "husky": "^0.14.3", 60 | "install": "^0.10.1", 61 | "json-loader": "^0.5.7", 62 | "live-server": "^1.2.0", 63 | "prettier": "^1.6.1", 64 | "pug-cli": "^1.0.0-alpha6", 65 | "stats-js": "^1.0.0-alpha1", 66 | "ts-loader": "^2.3.7", 67 | "tslint": "^5.7.0", 68 | "tslint-config-prettier": "^1.5.0", 69 | "typescript": "^2.5.2", 70 | "webpack": "^3.5.6" 71 | }, 72 | "dependencies": { 73 | "@types/dat-gui": "^0.6.3", 74 | "bezier-js": "^2.2.3", 75 | "dat-gui": "^0.5.0", 76 | "file-name": "^0.1.0", 77 | "gl-matrix": "^2.4.0", 78 | "parse-hdr": "^1.0.0", 79 | "query-string": "^5.0.0", 80 | "shelljs": "^0.7.8", 81 | "simplex-noise": "^2.3.0", 82 | "uuid": "^3.1.0", 83 | "webgl-obj-loader": "^0.1.1" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pug.config.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs'); 2 | const pkg = require('./package.json'); 3 | const examples = require('./examples/examples.json'); 4 | 5 | const production = process.env.NODE_ENV === 'production'; 6 | 7 | const watch = production ? '' : '-w'; 8 | 9 | // To make developing an example faster 10 | // set the env variable EXAMPLE e.g: 11 | // EXAMPLE=linegeometry npm run start 12 | const exampleDir = process.env.EXAMPLE; 13 | 14 | let entry = './examples/src/templates/[!_]*.pug'; 15 | 16 | if (exampleDir !== undefined) { 17 | entry = `./examples/src/templates/${exampleDir}.pug`; 18 | } 19 | 20 | const obj = JSON.stringify({ 21 | description: pkg.description, 22 | examples 23 | }); 24 | 25 | const cmd = `pug --silent --obj '${obj}' ${watch} ${entry} --pretty --out ./examples`; 26 | 27 | shell.exec(cmd, code => { 28 | if (code !== 0) { 29 | console.log('Pug cli: error'); // eslint-disable-line no-console 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /src/cameras/Camera.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import { RENDERER_DEFAULT_RATIO } from '../core/Constants'; 3 | import Object3D from '../core/Object3D'; 4 | import Vector3 from '../math/Vector3'; 5 | 6 | interface Options { 7 | near?: number; 8 | far?: number; 9 | fov?: number; 10 | position?: Vector3; 11 | target?: Vector3; 12 | up?: Vector3; 13 | } 14 | 15 | export default class Camera { 16 | public projectionMatrix: mat4; 17 | public worldInverseMatrix: mat4; 18 | public isCamera: boolean; 19 | public isPespectiveCamera: boolean; 20 | public isOrthographicCamera: boolean; 21 | public near: number; 22 | public far: number; 23 | public fov: number; 24 | public aspect: number; 25 | public position: Vector3; 26 | public target: Vector3; 27 | public up: Vector3; 28 | 29 | constructor(options: Options) { 30 | this.projectionMatrix = mat4.create(); 31 | this.worldInverseMatrix = mat4.create(); 32 | this.isCamera = true; 33 | this.isPespectiveCamera = false; 34 | this.isOrthographicCamera = false; 35 | this.near = 0.1; 36 | this.far = 100; 37 | this.fov = 70; 38 | this.aspect = RENDERER_DEFAULT_RATIO; 39 | this.position = new Vector3(); 40 | this.target = new Vector3(); 41 | this.up = new Vector3(0, 1, 0); 42 | Object.assign(this, options); 43 | } 44 | 45 | public lookAt(x = 0, y = 0, z = 0) { 46 | this.target.set(x, y, z); 47 | } 48 | 49 | public updateMatrixWorld() { 50 | mat4.identity(this.worldInverseMatrix); 51 | mat4.lookAt( 52 | this.worldInverseMatrix, 53 | this.position.v, 54 | this.target.v, 55 | this.up.v 56 | ); 57 | } 58 | 59 | public updateProjectionMatrix() { 60 | // override 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/cameras/OrthographicCamera.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import Vector3 from '../math/Vector3'; 3 | import Camera from './Camera'; 4 | 5 | interface Options { 6 | left?: number; 7 | right?: number; 8 | bottom?: number; 9 | top?: number; 10 | near?: number; 11 | far?: number; 12 | fov?: number; 13 | position?: Vector3; 14 | target?: Vector3; 15 | up?: Vector3; 16 | } 17 | 18 | export default class OrthographicCamera extends Camera { 19 | public left: number; 20 | public right: number; 21 | public bottom: number; 22 | public top: number; 23 | constructor(options: Options = {}) { 24 | super(options); 25 | this.left = options.left || -1; 26 | this.right = options.right || 1; 27 | this.bottom = options.bottom || -1; 28 | this.top = options.top || 1; 29 | this.isOrthographicCamera = true; 30 | } 31 | 32 | public updateProjectionMatrix() { 33 | mat4.ortho( 34 | this.projectionMatrix, 35 | this.left, 36 | this.right, 37 | this.bottom, 38 | this.top, 39 | this.near, 40 | this.far 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/cameras/PerspectiveCamera.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import { RENDERER_DEFAULT_RATIO } from '../core/Constants'; 3 | import Vector3 from '../math/Vector3'; 4 | import Camera from './Camera'; 5 | 6 | interface Options { 7 | near?: number; 8 | far?: number; 9 | fov?: number; 10 | position?: Vector3; 11 | target?: Vector3; 12 | up?: Vector3; 13 | } 14 | 15 | export default class PerspectiveCamera extends Camera { 16 | constructor(options: Options) { 17 | super(options); 18 | this.isPespectiveCamera = true; 19 | } 20 | 21 | public updateProjectionMatrix() { 22 | mat4.perspective( 23 | this.projectionMatrix, 24 | this.fov, 25 | this.aspect, 26 | this.near, 27 | this.far 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/core/Capabilities.ts: -------------------------------------------------------------------------------- 1 | import { warn } from '../utils/Console'; 2 | import { PRECISION } from './Constants'; 3 | import * as GL from './GL'; 4 | 5 | /* 6 | * https://github.com/mrdoob/three.js/blob/dev/src/renderers/webgl/WebGLCapabilities.js 7 | */ 8 | 9 | function getMaxPrecision( 10 | gl: WebGL2RenderingContext | WebGLRenderingContext, 11 | precision: string 12 | ) { 13 | if (precision === 'highp') { 14 | if ( 15 | gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision > 16 | 0 && 17 | gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).precision > 18 | 0 19 | ) { 20 | return 'highp'; 21 | } 22 | precision = 'mediump'; 23 | } 24 | 25 | if (precision === 'mediump') { 26 | if ( 27 | gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT).precision > 28 | 0 && 29 | gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT) 30 | .precision > 0 31 | ) { 32 | return 'mediump'; 33 | } 34 | } 35 | return 'lowp'; 36 | } 37 | 38 | function Capabilities(gl: WebGL2RenderingContext | WebGLRenderingContext) { 39 | let precision = PRECISION; 40 | const maxPrecision = getMaxPrecision(gl, precision); 41 | 42 | if (maxPrecision !== precision) { 43 | warn( 44 | 'Capabilities:', 45 | precision, 46 | 'not supported, using', 47 | maxPrecision, 48 | 'instead.' 49 | ); 50 | precision = maxPrecision; 51 | } 52 | 53 | const maxTextures: number = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); 54 | const maxVertexTextures: number = gl.getParameter( 55 | gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS 56 | ); 57 | const maxTextureSize: number = gl.getParameter(gl.MAX_TEXTURE_SIZE); 58 | const maxCubemapSize: number = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); 59 | 60 | const maxAttributes: number = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); 61 | const maxVertexUniforms: number = gl.getParameter( 62 | gl.MAX_VERTEX_UNIFORM_VECTORS 63 | ); 64 | const maxVaryings: number = gl.getParameter(gl.MAX_VARYING_VECTORS); 65 | const maxFragmentUniforms: number = gl.getParameter( 66 | gl.MAX_FRAGMENT_UNIFORM_VECTORS 67 | ); 68 | 69 | return { 70 | maxAttributes, 71 | maxCubemapSize, 72 | maxFragmentUniforms, 73 | maxPrecision, 74 | maxTextures, 75 | maxTextureSize, 76 | maxVertexTextures, 77 | maxVertexUniforms, 78 | maxVaryings, 79 | precision 80 | }; 81 | } 82 | 83 | function Extensions(gl: WebGL2RenderingContext | WebGLRenderingContext) { 84 | const vertexArrayObject = 85 | GL.webgl2 || gl.getExtension('OES_vertex_array_object') || false; 86 | const angleInstancedArrays = 87 | gl.getExtension('ANGLE_instanced_arrays') || false; 88 | const textureFloat = gl.getExtension('OES_texture_float') || false; 89 | 90 | return { 91 | angleInstancedArrays, 92 | textureFloat, 93 | vertexArrayObject 94 | }; 95 | } 96 | 97 | let capabilities: any = {}; 98 | let extensions: any = {}; 99 | 100 | /* 101 | Set the capabilities once 102 | */ 103 | export function set(gl: WebGL2RenderingContext | WebGLRenderingContext) { 104 | capabilities = Capabilities(gl); 105 | extensions = Extensions(gl); 106 | } 107 | 108 | /* 109 | Get capabilities 110 | */ 111 | export { capabilities, extensions }; 112 | -------------------------------------------------------------------------------- /src/core/Constants.ts: -------------------------------------------------------------------------------- 1 | // Contexts 2 | export const WEBGL_CONTEXT: string = 'webgl'; 3 | export const WEBGL2_CONTEXT: string = 'webgl2'; 4 | 5 | // Default ratio 6 | export const RENDERER_DEFAULT_CONTEXT: string = WEBGL2_CONTEXT; 7 | export const RENDERER_DEFAULT_WIDTH: number = 1280; 8 | export const RENDERER_DEFAULT_HEIGHT: number = 720; 9 | export const RENDERER_DEFAULT_RATIO: number = 10 | RENDERER_DEFAULT_WIDTH / RENDERER_DEFAULT_HEIGHT; 11 | 12 | // Precision 13 | export const PRECISION: string = 'highp'; 14 | 15 | // Culling 16 | export const CULL_NONE: number = -1; 17 | export const CULL_BACK: number = 0x0405; 18 | export const CULL_FRONT: number = 0x0404; 19 | export const CULL_FRONT_AND_BACK: number = 0x0408; 20 | 21 | // Draw style 22 | export const DRAW_POINTS: number = 0; 23 | export const DRAW_LINES: number = 1; 24 | export const DRAW_LINE_LOOP: number = 2; 25 | export const DRAW_LINE_STRIP: number = 3; 26 | export const DRAW_TRIANGLES: number = 4; 27 | 28 | // Uniform buffer location indices 29 | export const UNIFORM_PROJECTION_VIEW_LOCATION: number = 0; 30 | export const UNIFORM_AMBIENT_LIGHT_LOCATION: number = 1; 31 | export const UNIFORM_DIRECTIONAL_LIGHTS_LOCATION: number = 2; 32 | export const UNIFORM_POINT_LIGHTS_LOCATION: number = 3; 33 | 34 | // Material types 35 | export const MATERIAL_BASIC = 'basic'; 36 | export const MATERIAL_LAMBERT = 'lambert'; 37 | export const MATERIAL_PHONG = 'phong'; 38 | 39 | export const LIGHT_AMBIENT = 'ambient'; 40 | export const LIGHT_DIRECTIONAL = 'directional'; 41 | export const LIGHT_POINT = 'point'; 42 | -------------------------------------------------------------------------------- /src/core/EventDispatcher.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/arboleya/happens 2 | 3 | export default class EventDispatcher { 4 | public listeners: any[]; 5 | 6 | public on(event: string, fn: () => void) { 7 | this.validate(fn); 8 | this.init(event).push(fn); 9 | } 10 | 11 | public off(event: string, fn: () => void) { 12 | const pool = this.init(event); 13 | pool.splice(pool.indexOf(fn), 1); 14 | } 15 | 16 | public once(event: string, fn: () => void) { 17 | this.validate(fn); 18 | const wrapper = () => { 19 | this.off(event, wrapper); 20 | fn.apply(this, arguments); 21 | }; 22 | this.on(event, wrapper); 23 | } 24 | 25 | public emit(event: string, ...args) { 26 | const pool = this.init(event).slice(0); 27 | for (const i in pool) pool[i].apply(this, [].slice.call(arguments, 1)); 28 | } 29 | 30 | private validate(fn: () => void) { 31 | if (!(fn && fn instanceof Function)) 32 | throw new Error(fn + ' is not a Function'); 33 | } 34 | 35 | private init(event: string) { 36 | const tmp = this.listeners || (this.listeners = []); 37 | return tmp[event] || (tmp[event] = []); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/core/GL.ts: -------------------------------------------------------------------------------- 1 | import { WEBGL2_CONTEXT } from './Constants'; 2 | 3 | let gl: WebGL2RenderingContext | WebGLRenderingContext; 4 | let contextType: string; 5 | let webgl2: boolean; 6 | 7 | /* 8 | Set the gl instance 9 | This is set from the renderer 10 | */ 11 | export function set( 12 | _gl: WebGL2RenderingContext | WebGLRenderingContext, 13 | _contextType: string 14 | ) { 15 | gl = _gl; 16 | contextType = _contextType; 17 | webgl2 = contextType === WEBGL2_CONTEXT; 18 | } 19 | 20 | /* 21 | Get the gl instance 22 | */ 23 | export function get(): WebGL2RenderingContext | WebGLRenderingContext { 24 | return gl; 25 | } 26 | 27 | /** 28 | * createBuffer 29 | * @return {Buffer} 30 | */ 31 | function createBuffer( 32 | type: GLenum, 33 | data: Float32Array | Uint16Array, 34 | isDynamic: boolean = false 35 | ) { 36 | const buffer = gl.createBuffer(); 37 | const usage = isDynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW; 38 | const ArrayView = type === gl.ARRAY_BUFFER ? Float32Array : Uint16Array; 39 | gl.bindBuffer(type, buffer); 40 | // https://github.com/nkemnitz/webgl2-ts/blob/master/WebGL2/webgl2-context.d.ts#L276 41 | if (gl instanceof WebGL2RenderingContext) { 42 | gl.bufferData(type, new ArrayView(data), usage, 0); 43 | } else { 44 | gl.bufferData(type, new ArrayView(data), usage); 45 | } 46 | gl.bindBuffer(type, null); 47 | return buffer; 48 | } 49 | 50 | /** 51 | * createUniformBuffer 52 | * @return {Buffer} 53 | */ 54 | function createUniformBuffer(data: Float32Array) { 55 | const buffer = gl.createBuffer(); 56 | if (gl instanceof WebGL2RenderingContext) { 57 | gl.bindBuffer(gl.UNIFORM_BUFFER, buffer); 58 | gl.bufferData(gl.UNIFORM_BUFFER, new Float32Array(data), gl.DYNAMIC_DRAW); 59 | gl.bindBuffer(gl.UNIFORM_BUFFER, null); 60 | return buffer; 61 | } else { 62 | return false; 63 | } 64 | } 65 | 66 | export { webgl2, createBuffer, createUniformBuffer }; 67 | -------------------------------------------------------------------------------- /src/core/ImageData.ts: -------------------------------------------------------------------------------- 1 | export default class ImageData { 2 | public width: number; 3 | public height: number; 4 | public data: Float32Array; 5 | 6 | constructor(width: number, height: number, data: Float32Array) { 7 | this.width = width; 8 | this.height = height; 9 | this.data = data; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/core/Object3D.ts: -------------------------------------------------------------------------------- 1 | import { mat4, quat, vec3 } from 'gl-matrix'; 2 | 3 | import Camera from '../cameras/Camera'; 4 | import OrthographicCamera from '../cameras/OrthographicCamera'; 5 | import PerspectiveCamera from '../cameras/PerspectiveCamera'; 6 | import { lookAt } from '../math/Utils'; 7 | import Vector3 from '../math/Vector3'; 8 | 9 | let axisAngle = 0; 10 | const quaternionAxisAngle = vec3.create(); 11 | 12 | export default class Object3D { 13 | public children: Object3D[]; 14 | public localMatrix: mat4; 15 | public modelMatrix: mat4; 16 | public modelViewMatrix: mat4; 17 | public position: Vector3; 18 | public rotation: Vector3; 19 | public scale: Vector3; 20 | public isObject3D: boolean; 21 | public parent: Object3D; 22 | public matrixAutoUpdate: boolean; 23 | public quaternion: quat; 24 | public quaternionLookAt: quat; 25 | public lookAtUp: vec3; 26 | 27 | constructor() { 28 | this.children = []; 29 | this.localMatrix = mat4.create(); 30 | this.modelMatrix = mat4.create(); 31 | this.modelViewMatrix = mat4.create(); 32 | this.matrixAutoUpdate = true; 33 | this.position = new Vector3(); 34 | this.rotation = new Vector3(); 35 | this.scale = new Vector3(1, 1, 1); 36 | this.isObject3D = true; 37 | this.quaternion = quat.create(); 38 | this.quaternionLookAt = quat.create(); 39 | this.lookAtUp = vec3.create(); // needs to be [0, 0, 0] although it should be [0, 1, 0] 40 | } 41 | 42 | public updateMatrix(camera: Camera | PerspectiveCamera | OrthographicCamera) { 43 | mat4.identity(this.modelViewMatrix); 44 | 45 | if (this.matrixAutoUpdate) { 46 | // Reset 47 | mat4.identity(this.localMatrix); 48 | mat4.identity(this.modelMatrix); 49 | quat.identity(this.quaternion); 50 | 51 | // If Object3D has a parent, copy the computed modelMatrix into localMatrix 52 | if (this.parent) { 53 | mat4.copy(this.localMatrix, this.parent.modelMatrix); 54 | mat4.multiply(this.modelMatrix, this.modelMatrix, this.localMatrix); 55 | } 56 | 57 | // Use lookAt quat as base 58 | // Note: this.rotation isn't updated if lookAt's used 59 | quat.copy(this.quaternion, this.quaternionLookAt); 60 | 61 | // Apply local transitions to modelMatrix 62 | mat4.translate(this.modelMatrix, this.modelMatrix, this.position.v); 63 | quat.rotateX(this.quaternion, this.quaternion, this.rotation.x); 64 | quat.rotateY(this.quaternion, this.quaternion, this.rotation.y); 65 | quat.rotateZ(this.quaternion, this.quaternion, this.rotation.z); 66 | axisAngle = quat.getAxisAngle(quaternionAxisAngle, this.quaternion); 67 | mat4.rotate( 68 | this.modelMatrix, 69 | this.modelMatrix, 70 | axisAngle, 71 | quaternionAxisAngle 72 | ); 73 | mat4.scale(this.modelMatrix, this.modelMatrix, this.scale.v); 74 | } 75 | 76 | // Model View Matrix 77 | if (camera) { 78 | mat4.multiply( 79 | this.modelViewMatrix, 80 | camera.worldInverseMatrix, 81 | this.modelMatrix 82 | ); 83 | } 84 | } 85 | 86 | public lookAt(target: Vector3) { 87 | quat.identity(this.quaternionLookAt); 88 | this.quaternionLookAt = lookAt(this.position.v, target.v, this.lookAtUp); 89 | } 90 | 91 | public setParent(parent: Object3D) { 92 | this.unParent(); 93 | if (parent.isObject3D) { 94 | parent.children.push(this); 95 | this.parent = parent; 96 | } 97 | } 98 | 99 | public unParent() { 100 | if (this.parent === undefined) return; 101 | const objectIndex = this.parent.children.indexOf(this); 102 | if (objectIndex !== -1) { 103 | this.parent.children.splice(objectIndex, 1); 104 | this.parent = null; 105 | } 106 | } 107 | 108 | public dispose() { 109 | this.unParent(); 110 | this.children = []; 111 | this.localMatrix = null; 112 | this.modelMatrix = null; 113 | this.position = null; 114 | this.rotation = null; 115 | this.scale = null; 116 | this.quaternion = null; 117 | this.isObject3D = null; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/core/Raycaster.ts: -------------------------------------------------------------------------------- 1 | import { mat4, vec2, vec3 } from 'gl-matrix'; 2 | import PerspectiveCamera from '../cameras/PerspectiveCamera'; 3 | import Face from '../geometry/Face'; 4 | import Ray from '../math/Ray'; 5 | import Sphere from '../math/Sphere'; 6 | import { barycoordFromPoint } from '../math/Utils'; 7 | import Vector2 from '../math/Vector2'; 8 | import Vector3 from '../math/Vector3'; 9 | import Mesh from './Mesh'; 10 | import Scene from './Scene'; 11 | 12 | const inversedProjectionViewMatrix: mat4 = mat4.create(); 13 | const cameraDirection: vec3 = vec3.create(); 14 | const directionVector = new Vector3(); 15 | 16 | let barycoord: Vector3; 17 | const fvA = new Vector3(); 18 | const fvB = new Vector3(); 19 | const fvC = new Vector3(); 20 | const uvA = new Vector2(); 21 | const uvB = new Vector2(); 22 | const uvC = new Vector2(); 23 | const sphere = new Sphere(); 24 | 25 | export default class RayCaster { 26 | public ray: Ray; 27 | public near: number; 28 | public far: number; 29 | 30 | constructor(origin: Vector3, direction: Vector3, near: number, far: number) { 31 | this.ray = new Ray(); 32 | this.near = near || 0; 33 | this.far = far || Infinity; 34 | } 35 | 36 | public setFromCamera( 37 | coords: Vector2, 38 | scene: Scene, 39 | camera: PerspectiveCamera, 40 | object: Mesh 41 | ) { 42 | if (camera && camera.isPespectiveCamera) { 43 | this.ray.origin.copy(camera.position); 44 | 45 | vec3.copy(cameraDirection, [coords.x, coords.y, 0.5]); 46 | 47 | mat4.multiply( 48 | inversedProjectionViewMatrix, 49 | camera.projectionMatrix, 50 | camera.worldInverseMatrix 51 | ); 52 | mat4.invert(inversedProjectionViewMatrix, inversedProjectionViewMatrix); 53 | 54 | vec3.transformMat4( 55 | cameraDirection, 56 | cameraDirection, 57 | inversedProjectionViewMatrix 58 | ); 59 | 60 | vec3.sub(cameraDirection, cameraDirection, camera.position.v); 61 | vec3.normalize(cameraDirection, cameraDirection); 62 | 63 | directionVector.set( 64 | cameraDirection[0], 65 | cameraDirection[1], 66 | cameraDirection[2] 67 | ); 68 | 69 | this.ray.direction.copy(directionVector); 70 | } 71 | } 72 | 73 | public uvIntersection(point: Vector3, v0: Vector3, v1: Vector3, v2: Vector3) { 74 | barycoord = barycoordFromPoint(point.v, v0.v, v1.v, v2.v); 75 | uvA.scale(barycoord.x); 76 | uvB.scale(barycoord.y); 77 | uvC.scale(barycoord.z); 78 | uvA.add(uvB).add(uvC); 79 | return uvA.clone(); 80 | } 81 | 82 | public intersectObject(object: Mesh) { 83 | if (!object.visible) return; 84 | let intersect; 85 | let uv; 86 | let face; 87 | 88 | // Check sphere 89 | if (object.boundingSphere === undefined) object.computeBoundingSphere(); 90 | sphere.copy(object.boundingSphere); 91 | // Apply object modelMatrix, incase object has been transformed 92 | sphere.applyMatrix(object.modelMatrix); 93 | 94 | // Exit if the ray doesn't intersect the sphere 95 | if (!this.ray.intersectsSphere(sphere)) { 96 | return; 97 | } 98 | 99 | for (const f of object.geometry.faces) { 100 | vec3.copy(fvA.v, f.vertices[0].v); 101 | vec3.copy(fvB.v, f.vertices[1].v); 102 | vec3.copy(fvC.v, f.vertices[2].v); 103 | 104 | // Multiply vertices by object matrix 105 | vec3.transformMat4(fvA.v, fvA.v, object.modelMatrix); 106 | vec3.transformMat4(fvB.v, fvB.v, object.modelMatrix); 107 | vec3.transformMat4(fvC.v, fvC.v, object.modelMatrix); 108 | 109 | intersect = this.ray.intersectTriangle(fvA, fvB, fvC); 110 | 111 | if (intersect) { 112 | // Get uv intersection 113 | vec2.copy(uvA.v, object.geometry.uvs[f.uvs[0]].v); 114 | vec2.copy(uvB.v, object.geometry.uvs[f.uvs[1]].v); 115 | vec2.copy(uvC.v, object.geometry.uvs[f.uvs[2]].v); 116 | face = f; 117 | uv = this.uvIntersection(intersect, fvA, fvB, fvC); 118 | break; 119 | } 120 | } 121 | 122 | return intersect ? { point: intersect, uv, face } : null; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/core/Scene.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import Mesh from '../core/Mesh'; 3 | import Lights from '../lights/Lights'; 4 | 5 | export default class Scene { 6 | public objects: Mesh[]; 7 | public ambientLight: Lights; 8 | public pointLights: Lights; 9 | public directionalLights: Lights; 10 | 11 | constructor() { 12 | this.objects = []; 13 | this.pointLights = undefined; 14 | this.directionalLights = undefined; 15 | } 16 | 17 | public add(object: Mesh) { 18 | this.objects.push(object); 19 | } 20 | 21 | public remove(object: Mesh, dispose = false) { 22 | const objectIndex = this.objects.indexOf(object); 23 | if (objectIndex !== -1) { 24 | this.objects.splice(objectIndex, 1); 25 | if (dispose) { 26 | object.dispose(); 27 | object = undefined; 28 | } 29 | } 30 | } 31 | 32 | public update() { 33 | if (this.ambientLight) { 34 | this.ambientLight.update(); 35 | this.ambientLight.bind(); 36 | } 37 | if (this.directionalLights) { 38 | this.directionalLights.update(); 39 | this.directionalLights.bind(); 40 | } 41 | if (this.pointLights) { 42 | this.pointLights.update(); 43 | this.pointLights.bind(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/Texture.ts: -------------------------------------------------------------------------------- 1 | import EventDispatcher from '../core/EventDispatcher'; 2 | import HdrLoader from '../loaders/HdrLoader'; 3 | import ImageLoader from '../loaders/ImageLoader'; 4 | import { isPowerOf2, nearestPowerOf2 } from '../math/Utils'; 5 | import { createCanvas } from '../utils/Canvas'; 6 | import { warn } from '../utils/Console'; 7 | import * as GL from './GL'; 8 | import ImageData from './ImageData'; 9 | 10 | let gl: WebGL2RenderingContext | WebGLRenderingContext; 11 | 12 | interface Options { 13 | src?: number; 14 | magFilter?: number; 15 | minFilter?: number; 16 | wrapS?: number; 17 | wrapT?: number; 18 | resizeToPow2?: number; 19 | } 20 | 21 | export default class Texture extends EventDispatcher { 22 | public src: string; 23 | public magFilter: number; 24 | public minFilter: number; 25 | public wrapS: number; 26 | public wrapT: number; 27 | public resizeToPow2: boolean; 28 | public texture: WebGLTexture; 29 | public _isHdr: boolean; 30 | public image: HTMLImageElement | HTMLCanvasElement | ImageData; 31 | 32 | constructor(options: Options) { 33 | super(); 34 | gl = GL.get(); 35 | 36 | this.src = null; 37 | this.magFilter = gl.NEAREST; 38 | this.minFilter = gl.NEAREST; 39 | this.wrapS = gl.CLAMP_TO_EDGE; 40 | this.wrapT = gl.CLAMP_TO_EDGE; 41 | this.resizeToPow2 = false; 42 | 43 | Object.assign(this, options); 44 | 45 | const { canvas } = createCanvas(1, 1); 46 | 47 | this.texture = gl.createTexture(); 48 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 49 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); 50 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.magFilter); 51 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.minFilter); 52 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.wrapS); 53 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.wrapT); 54 | gl.bindTexture(gl.TEXTURE_2D, null); 55 | 56 | if (this.src) { 57 | this._isHdr = this.src.split('.').pop() === 'hdr'; 58 | this.load(this.src); 59 | } 60 | } 61 | 62 | public load(src: string) { 63 | if (this._isHdr) { 64 | HdrLoader(src) 65 | .then(this.onTextureLoaded) 66 | .catch(this.onTextureError); 67 | } else { 68 | ImageLoader(src) 69 | .then(this.onTextureLoaded) 70 | .catch(this.onTextureError); 71 | } 72 | } 73 | 74 | public onTextureLoaded = response => { 75 | this.image = response; 76 | this.update(this.image); 77 | this.emit('loaded'); 78 | }; 79 | 80 | public onTextureError = (error: string) => { 81 | warn(error); 82 | this.emit('error', error); 83 | }; 84 | 85 | public updateImage(src: string) { 86 | this.src = src || this.src; 87 | this.load(this.src); 88 | } 89 | 90 | public update(image: HTMLCanvasElement | HTMLImageElement | ImageData) { 91 | gl = GL.get(); 92 | 93 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 94 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 95 | if (image instanceof ImageData && gl instanceof WebGL2RenderingContext) { 96 | this.image = image; 97 | // This is only for hdr data texture atm 98 | gl.texImage2D( 99 | gl.TEXTURE_2D, 100 | 0, 101 | gl.RGBA16F, 102 | image.width, 103 | image.height, 104 | 0, 105 | gl.RGBA, 106 | gl.FLOAT, 107 | image.data 108 | ); 109 | } else if ( 110 | image instanceof HTMLCanvasElement || 111 | image instanceof HTMLImageElement 112 | ) { 113 | gl.texImage2D( 114 | gl.TEXTURE_2D, 115 | 0, 116 | gl.RGBA, 117 | gl.RGBA, 118 | gl.UNSIGNED_BYTE, 119 | image 120 | ); 121 | } 122 | gl.bindTexture(gl.TEXTURE_2D, null); 123 | } 124 | 125 | public _resizeImage(image) { 126 | if (!this.resizeToPow2 || image instanceof ImageData) return this.image; 127 | 128 | // Return if the image size is already a power of 2 129 | if (isPowerOf2(image.width) && isPowerOf2(image.height)) { 130 | return image; 131 | } 132 | 133 | const size = nearestPowerOf2(Math.max(image.width, image.height)); 134 | 135 | const { canvas, ctx } = createCanvas(size, size); 136 | ctx.drawImage(image, 0, 0, size, size); 137 | 138 | return canvas; 139 | } 140 | 141 | public dispose() { 142 | gl = GL.get(); 143 | gl.deleteTexture(this.texture); 144 | this.texture = null; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/core/Texture3d.ts: -------------------------------------------------------------------------------- 1 | import EventDispatcher from '../core/EventDispatcher'; 2 | import HdrLoader from '../loaders/HdrLoader'; 3 | import ImageLoader from '../loaders/ImageLoader'; 4 | import { warn } from '../utils/Console'; 5 | import * as GL from './GL'; 6 | import ImageData from './ImageData'; 7 | 8 | let gl: WebGL2RenderingContext | WebGLRenderingContext; 9 | 10 | interface Options { 11 | src: Uint8Array; 12 | size: number; 13 | } 14 | 15 | export default class Texture3d extends EventDispatcher { 16 | public src: Uint8Array; 17 | public size: number; 18 | public texture: WebGLTexture; 19 | 20 | constructor(options: Options) { 21 | super(); 22 | gl = GL.get(); 23 | 24 | if (!(gl instanceof WebGL2RenderingContext)) { 25 | return; 26 | } 27 | 28 | this.src = null; 29 | this.size = null; 30 | Object.assign(this, options); 31 | 32 | this.texture = gl.createTexture(); 33 | gl.activeTexture(gl.TEXTURE0); 34 | gl.bindTexture(gl.TEXTURE_3D, this.texture); 35 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_BASE_LEVEL, 0); 36 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAX_LEVEL, Math.log2(this.size)); 37 | gl.texParameteri( 38 | gl.TEXTURE_3D, 39 | gl.TEXTURE_MIN_FILTER, 40 | gl.LINEAR_MIPMAP_LINEAR 41 | ); 42 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 43 | 44 | gl.texImage3D( 45 | gl.TEXTURE_3D, 46 | 0, 47 | gl.R8, 48 | this.size, 49 | this.size, 50 | this.size, 51 | 0, 52 | gl.RED, 53 | gl.UNSIGNED_BYTE, 54 | this.src 55 | ); 56 | gl.generateMipmap(gl.TEXTURE_3D); 57 | } 58 | 59 | public dispose() { 60 | gl = GL.get(); 61 | if (!(gl instanceof WebGL2RenderingContext)) { 62 | return; 63 | } 64 | gl.deleteTexture(this.texture); 65 | this.texture = null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/core/TextureVideo.ts: -------------------------------------------------------------------------------- 1 | import EventDispatcher from '../core/EventDispatcher'; 2 | import { createCanvas } from '../utils/Canvas'; 3 | import * as GL from './GL'; 4 | 5 | let gl; 6 | 7 | interface VideoTextureOptions { 8 | src?: number; 9 | magFilter?: number; 10 | minFilter?: number; 11 | wrapS?: number; 12 | wrapT?: number; 13 | loop: boolean; 14 | autoplay: boolean; 15 | } 16 | 17 | export default class VideoTexture extends EventDispatcher { 18 | public src: string; 19 | public magFilter: number; 20 | public minFilter: number; 21 | public wrapS: number; 22 | public wrapT: number; 23 | public loop: boolean; 24 | public autoplay: boolean; 25 | public texture: WebGLTexture; 26 | public video: HTMLVideoElement; 27 | public _currentTime: number; 28 | 29 | constructor(options: VideoTextureOptions) { 30 | super(); 31 | gl = GL.get(); 32 | 33 | this.src = ''; 34 | this.magFilter = gl.NEAREST; 35 | this.minFilter = gl.NEAREST; 36 | this.wrapS = gl.CLAMP_TO_EDGE; 37 | this.wrapT = gl.CLAMP_TO_EDGE; 38 | this.loop = false; 39 | this.autoplay = true; 40 | 41 | Object.assign(this, options); 42 | 43 | this.video = document.createElement('video'); 44 | this.video.src = this.src; 45 | this.video.loop = this.loop; 46 | this.video.autoplay = this.autoplay; 47 | this.video.setAttribute('webkitplaysinline', 'webkitplaysinline'); 48 | this.video.setAttribute('playsinline', 'playsinline'); 49 | this.video.addEventListener('canplaythrough', this._onCanPlayThrough, true); 50 | this.video.addEventListener('ended', this._onEnded, true); 51 | this._currentTime = 0; 52 | 53 | const { canvas } = createCanvas(1, 1); 54 | this.texture = gl.createTexture(); 55 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 56 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); 57 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.magFilter); 58 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.minFilter); 59 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.wrapS); 60 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.wrapT); 61 | gl.bindTexture(gl.TEXTURE_2D, null); 62 | } 63 | 64 | public update() { 65 | gl = GL.get(); 66 | 67 | if (this.video.readyState >= this.video.HAVE_CURRENT_DATA) { 68 | if (this.video.currentTime !== this._currentTime) { 69 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 70 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 71 | gl.texImage2D( 72 | gl.TEXTURE_2D, 73 | 0, 74 | gl.RGBA, 75 | gl.RGBA, 76 | gl.UNSIGNED_BYTE, 77 | this.video 78 | ); 79 | gl.bindTexture(gl.TEXTURE_2D, null); 80 | } 81 | this._currentTime = this.video.currentTime; 82 | } 83 | } 84 | 85 | public _onCanPlayThrough = () => { 86 | this.emit('canplaythrough'); 87 | }; 88 | 89 | public _onEnded = () => { 90 | this.emit('ended'); 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /src/core/UniformBuffer.ts: -------------------------------------------------------------------------------- 1 | import { createUniformBuffer } from './GL'; 2 | 3 | export default class UniformBuffer { 4 | public data: Float32Array; 5 | public buffer: WebGLBuffer; 6 | 7 | constructor(data: Float32Array) { 8 | this.data = data; 9 | this.buffer = createUniformBuffer(data); 10 | } 11 | 12 | public setValues(values: Float32Array, offset = 0) { 13 | this.data.set(values, offset); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/core/UniformBuffers.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import UniformBuffer from './UniformBuffer'; 3 | 4 | // Global uniform buffers 5 | const uniformBuffers: any = {}; 6 | 7 | // Create buffers when gl context is ready 8 | export function setup() { 9 | // ProjectionView 10 | const projectionViewData = new Float32Array(mat4.create()); 11 | 12 | uniformBuffers.projectionView = new UniformBuffer(projectionViewData); 13 | } 14 | 15 | // Update projectionView buffer data 16 | export function updateProjectionView( 17 | gl: WebGL2RenderingContext, 18 | projectionMatrix: mat4 19 | ) { 20 | gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, uniformBuffers.projectionView.buffer); 21 | gl.bindBuffer(gl.UNIFORM_BUFFER, uniformBuffers.projectionView.buffer); 22 | 23 | const projectionViewData = [...projectionMatrix]; 24 | 25 | uniformBuffers.projectionView.data.set(projectionViewData, 0); 26 | 27 | gl.bufferSubData(gl.UNIFORM_BUFFER, 0, uniformBuffers.projectionView.data); 28 | gl.bindBuffer(gl.UNIFORM_BUFFER, null); 29 | } 30 | 31 | export default uniformBuffers; 32 | -------------------------------------------------------------------------------- /src/core/Vao.ts: -------------------------------------------------------------------------------- 1 | import { extensions } from './Capabilities'; 2 | import * as GL from './GL'; 3 | 4 | let gl: WebGL2RenderingContext | WebGLRenderingContext; 5 | 6 | export default class Vao { 7 | public vao: any; 8 | 9 | constructor() { 10 | gl = GL.get(); 11 | if (gl instanceof WebGL2RenderingContext) { 12 | this.vao = gl.createVertexArray(); 13 | } else if (extensions.vertexArrayObject) { 14 | this.vao = extensions.vertexArrayObject.createVertexArrayOES(); 15 | } 16 | } 17 | 18 | public bind() { 19 | if (gl instanceof WebGL2RenderingContext) { 20 | gl.bindVertexArray(this.vao); 21 | } else if (extensions.vertexArrayObject) { 22 | extensions.vertexArrayObject.bindVertexArrayOES(this.vao); 23 | } 24 | } 25 | 26 | public unbind() { 27 | if (gl instanceof WebGL2RenderingContext) { 28 | gl.bindVertexArray(null); 29 | } else if (extensions.vertexArrayObject) { 30 | extensions.vertexArrayObject.bindVertexArrayOES(null); 31 | } 32 | } 33 | 34 | public dispose() { 35 | if (gl instanceof WebGL2RenderingContext) { 36 | gl.deleteVertexArray(this.vao); 37 | } else if (extensions.vertexArrayObject) { 38 | extensions.vertexArrayObject.deleteVertexArrayOES(this.vao); 39 | } 40 | this.vao = null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/geometry/BufferAttribute.ts: -------------------------------------------------------------------------------- 1 | import * as GL from '../core/GL'; 2 | 3 | let gl: WebGL2RenderingContext | WebGLRenderingContext; 4 | 5 | export default class BufferAttribute { 6 | public type: number; 7 | public data: number[]; 8 | public itemSize: number; 9 | public numItems: number; 10 | public buffer: WebGLBuffer; 11 | public shaderAttribute: boolean; 12 | 13 | constructor( 14 | type: GLenum, 15 | data: any, // Float32Array | Uint16Array | Uint32Array, (typings are wrong for createBuffer) 16 | itemSize: number, 17 | shaderAttribute = true 18 | ) { 19 | this.type = type; 20 | this.itemSize = itemSize; 21 | this.numItems = data.length / itemSize; 22 | this.buffer = GL.createBuffer(type, data); 23 | this.shaderAttribute = shaderAttribute; 24 | } 25 | 26 | public bind() { 27 | gl = GL.get(); 28 | gl.bindBuffer(this.type, this.buffer); 29 | } 30 | 31 | public unbind() { 32 | gl = GL.get(); 33 | gl.bindBuffer(this.type, null); 34 | } 35 | 36 | public update(data: Float32Array) { 37 | this.bind(); 38 | gl = GL.get(); 39 | gl.bufferSubData(this.type, 0, data); 40 | this.unbind(); 41 | } 42 | 43 | public dispose() { 44 | gl = GL.get(); 45 | gl.deleteBuffer(this.buffer); 46 | this.buffer = null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/geometry/Face.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from 'gl-matrix'; 2 | import Vector3 from '../math/Vector3'; 3 | const cb = vec3.create(); 4 | const ab = vec3.create(); 5 | 6 | export default class Face { 7 | public indices: number[]; 8 | public vertices: Vector3[]; 9 | public uvs: number[]; 10 | public normal: Vector3; 11 | 12 | constructor( 13 | indiceA: number, 14 | indiceB: number, 15 | indiceC: number, 16 | vertexA: Vector3, 17 | vertexB: Vector3, 18 | vertexC: Vector3 19 | ) { 20 | this.indices = [indiceA, indiceB, indiceC]; 21 | this.vertices = [vertexA, vertexB, vertexC]; 22 | this.uvs = [indiceA, indiceB, indiceC]; 23 | this.normal = new Vector3(); 24 | this.updateFaceNormal(); 25 | } 26 | 27 | public updateFaceNormal() { 28 | // from threejs 29 | vec3.set(cb, 0, 0, 0); 30 | vec3.set(ab, 0, 0, 0); 31 | vec3.subtract(cb, this.vertices[2].v, this.vertices[1].v); 32 | vec3.subtract(ab, this.vertices[0].v, this.vertices[1].v); 33 | vec3.cross(cb, cb, ab); 34 | 35 | vec3.normalize(cb, cb); 36 | this.normal.set(cb[0], cb[1], cb[2]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/geometry/LineGeometry.ts: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry'; 2 | 3 | export default class LineGeometry extends Geometry { 4 | constructor(bufferVertices: number[] | Float32Array) { 5 | const vertices = []; 6 | let i3 = 0; 7 | let i6 = 0; 8 | const length = bufferVertices.length / 3; 9 | for (let i = 0; i < length; i += 1) { 10 | i3 = i * 3; 11 | i6 = i * 6; 12 | if (i < length - 1) { 13 | vertices[i6] = bufferVertices[i3]; 14 | vertices[i6 + 1] = bufferVertices[i3 + 1]; 15 | vertices[i6 + 2] = bufferVertices[i3 + 2]; 16 | vertices[i6 + 3] = bufferVertices[i3 + 3]; 17 | vertices[i6 + 4] = bufferVertices[i3 + 4]; 18 | vertices[i6 + 5] = bufferVertices[i3 + 5]; 19 | } 20 | } 21 | super(new Float32Array(vertices)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/geometry/PlaneGeometry.ts: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry'; 2 | 3 | export default class Plane extends Geometry { 4 | constructor( 5 | width = 1, 6 | height = 1, 7 | subdivisionsX = 1, 8 | subdivisionsY = 1, 9 | axis = 'XY', 10 | colors?: Float32Array 11 | ) { 12 | // https://github.com/yiwenl/Alfrid/blob/master/src/alfrid/Geom.js#L9 13 | 14 | // Note triangles are seperate... 15 | 16 | let vertices = []; 17 | const indices = []; 18 | let normals = []; 19 | let uvs = []; 20 | let index = 0; 21 | 22 | const spacerX = width / subdivisionsX; 23 | const spacerY = height / subdivisionsY; 24 | const offsetX = -width * 0.5; 25 | const offsetY = -height * 0.5; 26 | const spacerU = 1 / subdivisionsX; 27 | const spacerV = 1 / subdivisionsY; 28 | 29 | for (let y = 0; y < subdivisionsY; y += 1) { 30 | for (let x = 0; x < subdivisionsX; x += 1) { 31 | const triangleX = spacerX * x + offsetX; 32 | const triangleY = spacerY * y + offsetY; 33 | 34 | const u = x / subdivisionsX; 35 | const v = y / subdivisionsY; 36 | 37 | switch (axis) { 38 | case 'XZ': { 39 | // Facing towards y 40 | vertices = vertices.concat([triangleX, 0, triangleY]); 41 | vertices = vertices.concat([triangleX + spacerX, 0, triangleY]); 42 | vertices = vertices.concat([ 43 | triangleX + spacerX, 44 | 0, 45 | triangleY + spacerY 46 | ]); 47 | vertices = vertices.concat([triangleX, 0, triangleY + spacerY]); 48 | 49 | normals = normals.concat([0, 1, 0]); 50 | normals = normals.concat([0, 1, 0]); 51 | normals = normals.concat([0, 1, 0]); 52 | normals = normals.concat([0, 1, 0]); 53 | 54 | uvs = uvs.concat([u, 1 - v]); 55 | uvs = uvs.concat([u + spacerU, 1 - v]); 56 | uvs = uvs.concat([u + spacerU, 1 - (v + spacerV)]); 57 | uvs = uvs.concat([u, 1 - (v + spacerV)]); 58 | break; 59 | } 60 | case 'YZ': { 61 | // Facing towards x 62 | 63 | vertices = vertices.concat([0, triangleY, triangleX]); 64 | vertices = vertices.concat([0, triangleY, triangleX + spacerX]); 65 | vertices = vertices.concat([ 66 | 0, 67 | triangleY + spacerY, 68 | triangleX + spacerX 69 | ]); 70 | vertices = vertices.concat([0, triangleY + spacerY, triangleX]); 71 | 72 | normals = normals.concat([1, 0, 0]); 73 | normals = normals.concat([1, 0, 0]); 74 | normals = normals.concat([1, 0, 0]); 75 | normals = normals.concat([1, 0, 0]); 76 | 77 | uvs = uvs.concat([1 - u, v]); 78 | uvs = uvs.concat([1 - (u + spacerU), v]); 79 | uvs = uvs.concat([1 - (u + spacerU), v + spacerV]); 80 | uvs = uvs.concat([1 - u, v + spacerV]); 81 | break; 82 | } 83 | default: { 84 | // Facing towards z 85 | vertices = vertices.concat([triangleX, triangleY, 0]); 86 | vertices = vertices.concat([triangleX + spacerX, triangleY, 0]); 87 | vertices = vertices.concat([ 88 | triangleX + spacerX, 89 | triangleY + spacerY, 90 | 0 91 | ]); 92 | vertices = vertices.concat([triangleX, triangleY + spacerY, 0]); 93 | 94 | normals = normals.concat([0, 0, 1]); 95 | normals = normals.concat([0, 0, 1]); 96 | normals = normals.concat([0, 0, 1]); 97 | normals = normals.concat([0, 0, 1]); 98 | 99 | uvs = uvs.concat([u, v]); 100 | uvs = uvs.concat([u + spacerU, v]); 101 | uvs = uvs.concat([u + spacerU, v + spacerV]); 102 | uvs = uvs.concat([u, v + spacerV]); 103 | } 104 | } 105 | 106 | indices.push(index * 4 + 0); 107 | indices.push(index * 4 + 1); 108 | indices.push(index * 4 + 2); 109 | indices.push(index * 4 + 0); 110 | indices.push(index * 4 + 2); 111 | indices.push(index * 4 + 3); 112 | 113 | index += 1; 114 | } 115 | } 116 | 117 | super( 118 | new Float32Array(vertices), 119 | new Uint16Array(indices), 120 | new Float32Array(normals), 121 | new Float32Array(uvs), 122 | colors 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/geometry/SphereGeometry.ts: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry'; 2 | 3 | export default class SphereGeometry extends Geometry { 4 | constructor( 5 | radius = 1, 6 | axisDivisions = 8, 7 | heightDivisons = 8, 8 | colors?: Float32Array 9 | ) { 10 | // https://github.com/gpjt/webgl-lessons/blob/master/lesson12/index.html 11 | 12 | const vertices = []; 13 | const normals = []; 14 | const uvs = []; 15 | for (let axisNumber = 0; axisNumber <= axisDivisions; axisNumber += 1) { 16 | const theta = axisNumber * Math.PI / axisDivisions; 17 | const sinTheta = Math.sin(theta); 18 | const cosTheta = Math.cos(theta); 19 | for ( 20 | let heightNumber = 0; 21 | heightNumber <= heightDivisons; 22 | heightNumber += 1 23 | ) { 24 | const phi = heightNumber * 2 * Math.PI / heightDivisons; 25 | const sinPhi = Math.sin(phi); 26 | const cosPhi = Math.cos(phi); 27 | const x = cosPhi * sinTheta; 28 | const y = cosTheta; 29 | const z = sinPhi * sinTheta; 30 | const u = 1 - heightNumber / heightDivisons; 31 | const v = 1 - axisNumber / axisDivisions; 32 | normals.push(x); 33 | normals.push(y); 34 | normals.push(z); 35 | uvs.push(u); 36 | uvs.push(v); 37 | vertices.push(radius * x); 38 | vertices.push(radius * y); 39 | vertices.push(radius * z); 40 | } 41 | } 42 | 43 | const indices = []; 44 | for (let axisNumber = 0; axisNumber < axisDivisions; axisNumber += 1) { 45 | for ( 46 | let heightNumber = 0; 47 | heightNumber < heightDivisons; 48 | heightNumber += 1 49 | ) { 50 | const first = axisNumber * (heightDivisons + 1) + heightNumber; 51 | const second = first + heightDivisons + 1; 52 | indices.push(first); 53 | indices.push(second); 54 | indices.push(first + 1); 55 | indices.push(second); 56 | indices.push(second + 1); 57 | indices.push(first + 1); 58 | } 59 | } 60 | 61 | super( 62 | new Float32Array(vertices), 63 | new Uint16Array(indices), 64 | new Float32Array(normals), 65 | new Float32Array(uvs), 66 | colors 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/helpers/AxisHelper.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import Camera from '../cameras/Camera'; 3 | import OrthographicCamera from '../cameras/OrthographicCamera'; 4 | import PerspectiveCamera from '../cameras/PerspectiveCamera'; 5 | import { capabilities, extensions } from '../core/Capabilities'; 6 | import * as GL from '../core/GL'; 7 | import Material from '../core/Material'; 8 | import Mesh from '../core/Mesh'; 9 | import Geometry from '../geometry/Geometry'; 10 | import EsVersion from '../shaders/chunks/EsVersion.glsl'; 11 | import ProjectionView from '../shaders/chunks/ProjectionView.glsl'; 12 | 13 | let gl: WebGL2RenderingContext | WebGLRenderingContext; 14 | 15 | const vertexShaderEs300 = `${EsVersion} 16 | layout (location = 0) in vec3 aVertexPosition; 17 | layout (location = 1) in vec3 aVertexColor; 18 | 19 | ${ProjectionView} 20 | 21 | uniform mat4 uModelViewMatrix; 22 | 23 | out vec3 vColor; 24 | 25 | void main(void){ 26 | vColor = aVertexColor; 27 | gl_Position = uProjectionView.projectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); 28 | } 29 | `; 30 | 31 | const vertexShaderEs100 = ` 32 | attribute vec3 aVertexPosition; 33 | attribute vec3 aVertexColor; 34 | 35 | uniform mat4 uProjectionMatrix; 36 | uniform mat4 uModelViewMatrix; 37 | 38 | varying vec3 vColor; 39 | 40 | void main(void){ 41 | vColor = aVertexColor; 42 | gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); 43 | } 44 | `; 45 | 46 | function fragmentShaderEs300() { 47 | return `${EsVersion} 48 | precision ${capabilities.precision} float; 49 | in vec3 vColor; 50 | out vec4 outgoingColor; 51 | 52 | void main(void){ 53 | outgoingColor = vec4(vColor, 1.0); 54 | } 55 | `; 56 | } 57 | 58 | function fragmentShaderEs100() { 59 | return ` 60 | precision ${capabilities.precision} float; 61 | varying vec3 vColor; 62 | 63 | void main(void){ 64 | gl_FragColor = vec4(vColor, 1.0); 65 | } 66 | `; 67 | } 68 | 69 | class AxisGeometry extends Geometry { 70 | constructor(size: number) { 71 | let vertices = []; 72 | 73 | // X 74 | vertices = vertices.concat([0, 0, 0, size, 0, 0]); 75 | // Y 76 | vertices = vertices.concat([0, 0, 0, 0, size, 0]); 77 | // Z 78 | vertices = vertices.concat([0, 0, 0, 0, 0, size]); 79 | 80 | // Colors 81 | const colors = new Float32Array([ 82 | 1, 83 | 0, 84 | 0, 85 | 1, 86 | 0, 87 | 0, 88 | 0, 89 | 1, 90 | 0, 91 | 0, 92 | 1, 93 | 0, 94 | 0, 95 | 0, 96 | 1, 97 | 0, 98 | 0, 99 | 1 100 | ]); 101 | super(new Float32Array(vertices), undefined, undefined, undefined, colors); 102 | } 103 | } 104 | 105 | export default class AxisHelper extends Mesh { 106 | constructor(size = 1) { 107 | const vertexShader = GL.webgl2 ? vertexShaderEs300 : vertexShaderEs100; 108 | const fragmentShader = GL.webgl2 109 | ? fragmentShaderEs300() 110 | : fragmentShaderEs100(); 111 | super( 112 | new AxisGeometry(size), 113 | new Material({ 114 | name: 'AxisHelper', 115 | vertexShader, 116 | fragmentShader 117 | }) 118 | ); 119 | } 120 | 121 | public draw(camera: Camera | PerspectiveCamera | OrthographicCamera) { 122 | if (!this.visible) return; 123 | gl = GL.get(); 124 | 125 | // Update modelMatrix 126 | this.updateMatrix(camera); 127 | 128 | this.material.program.bind(); 129 | this.material.setUniforms( 130 | camera.projectionMatrix, 131 | this.modelViewMatrix, 132 | this.modelMatrix, 133 | camera 134 | ); 135 | 136 | if (extensions.vertexArrayObject) { 137 | this.vao.bind(); 138 | } else { 139 | this.bindAttributes(); 140 | this.bindAttributesInstanced(); 141 | this.bindIndexBuffer(); 142 | } 143 | 144 | gl.drawArrays( 145 | gl.LINES, 146 | 0, 147 | this.geometry.attributes.aVertexPosition.numItems 148 | ); 149 | 150 | if (extensions.vertexArrayObject) { 151 | this.vao.unbind(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/helpers/GridHelper.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import Camera from '../cameras/Camera'; 3 | import OrthographicCamera from '../cameras/OrthographicCamera'; 4 | import PerspectiveCamera from '../cameras/PerspectiveCamera'; 5 | import { capabilities, extensions } from '../core/Capabilities'; 6 | import * as GL from '../core/GL'; 7 | import Material from '../core/Material'; 8 | import Mesh from '../core/Mesh'; 9 | import Geometry from '../geometry/Geometry'; 10 | import { lerp } from '../math/Utils'; 11 | import EsVersion from '../shaders/chunks/EsVersion.glsl'; 12 | import ProjectionView from '../shaders/chunks/ProjectionView.glsl'; 13 | 14 | let gl: WebGL2RenderingContext | WebGLRenderingContext; 15 | 16 | const vertexShaderEs300 = `${EsVersion} 17 | ${ProjectionView} 18 | 19 | in vec3 aVertexPosition; 20 | 21 | uniform mat4 uModelViewMatrix; 22 | 23 | void main(void){ 24 | gl_Position = uProjectionView.projectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); 25 | } 26 | `; 27 | 28 | const vertexShaderEs100 = ` 29 | attribute vec3 aVertexPosition; 30 | 31 | uniform mat4 uProjectionMatrix; 32 | uniform mat4 uModelViewMatrix; 33 | 34 | void main(void){ 35 | gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); 36 | } 37 | `; 38 | 39 | function fragmentShaderEs300() { 40 | return `${EsVersion} 41 | precision ${capabilities.precision} float; 42 | out vec4 outgoingColor; 43 | 44 | void main(void){ 45 | outgoingColor = vec4(vec3(0.5), 1.0); 46 | } 47 | `; 48 | } 49 | 50 | function fragmentShaderEs100() { 51 | return ` 52 | precision ${capabilities.precision} float; 53 | 54 | void main(void){ 55 | gl_FragColor = vec4(vec3(0.5), 1.0); 56 | } 57 | `; 58 | } 59 | 60 | class GridGeometry extends Geometry { 61 | constructor(size: number, divisions: number) { 62 | let vertices = []; 63 | const halfSize = size * 0.5; 64 | 65 | for (let i = 0; i < divisions + 1; i += 1) { 66 | const x1 = lerp(-halfSize, halfSize, i / divisions); 67 | const y1 = 0; 68 | const z1 = -halfSize; 69 | const x2 = lerp(-halfSize, halfSize, i / divisions); 70 | const y2 = 0; 71 | const z2 = halfSize; 72 | vertices = vertices.concat([x1, y1, z1, x2, y2, z2]); 73 | } 74 | 75 | for (let i = 0; i < divisions + 1; i += 1) { 76 | const x1 = -halfSize; 77 | const y1 = 0; 78 | const z1 = lerp(-halfSize, halfSize, i / divisions); 79 | const x2 = halfSize; 80 | const y2 = 0; 81 | const z2 = lerp(-halfSize, halfSize, i / divisions); 82 | vertices = vertices.concat([x1, y1, z1, x2, y2, z2]); 83 | } 84 | 85 | super(new Float32Array(vertices)); 86 | } 87 | } 88 | 89 | export default class GridHelper extends Mesh { 90 | constructor(size = 1, divisions = 10) { 91 | const vertexShader = GL.webgl2 ? vertexShaderEs300 : vertexShaderEs100; 92 | const fragmentShader = GL.webgl2 93 | ? fragmentShaderEs300() 94 | : fragmentShaderEs100(); 95 | super( 96 | new GridGeometry(size, divisions), 97 | new Material({ 98 | name: 'GridHelper', 99 | vertexShader, 100 | fragmentShader 101 | }) 102 | ); 103 | } 104 | 105 | public draw(camera: Camera | PerspectiveCamera | OrthographicCamera) { 106 | if (!this.visible) return; 107 | gl = GL.get(); 108 | 109 | // Update modelMatrix 110 | this.updateMatrix(camera); 111 | 112 | this.material.program.bind(); 113 | this.material.setUniforms( 114 | camera.projectionMatrix, 115 | this.modelViewMatrix, 116 | this.modelMatrix, 117 | camera 118 | ); 119 | 120 | if (extensions.vertexArrayObject) { 121 | this.vao.bind(); 122 | } else { 123 | this.bindAttributes(); 124 | this.bindAttributesInstanced(); 125 | this.bindIndexBuffer(); 126 | } 127 | 128 | gl.drawArrays( 129 | gl.LINES, 130 | 0, 131 | this.geometry.attributes.aVertexPosition.numItems 132 | ); 133 | 134 | if (extensions.vertexArrayObject) { 135 | this.vao.unbind(); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/helpers/NormalsHelper.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import Camera from '../cameras/Camera'; 3 | import OrthographicCamera from '../cameras/OrthographicCamera'; 4 | import PerspectiveCamera from '../cameras/PerspectiveCamera'; 5 | import { capabilities, extensions } from '../core/Capabilities'; 6 | import * as GL from '../core/GL'; 7 | import Material from '../core/Material'; 8 | import Mesh from '../core/Mesh'; 9 | import Geometry from '../geometry/Geometry'; 10 | import EsVersion from '../shaders/chunks/EsVersion.glsl'; 11 | import ProjectionView from '../shaders/chunks/ProjectionView.glsl'; 12 | 13 | let gl: WebGL2RenderingContext | WebGLRenderingContext; 14 | 15 | const vertexShaderEs300 = `${EsVersion} 16 | ${ProjectionView} 17 | 18 | in vec3 aVertexPosition; 19 | 20 | uniform mat4 uModelViewMatrix; 21 | uniform mat3 uNormalMatrix; 22 | 23 | void main(void){ 24 | gl_Position = uProjectionView.projectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); 25 | } 26 | `; 27 | 28 | const vertexShaderEs100 = ` 29 | attribute vec3 aVertexPosition; 30 | 31 | uniform mat4 uProjectionMatrix; 32 | uniform mat4 uModelViewMatrix; 33 | uniform mat3 uNormalMatrix; 34 | 35 | void main(void){ 36 | gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); 37 | } 38 | `; 39 | 40 | function fragmentShaderEs300() { 41 | return `${EsVersion} 42 | precision ${capabilities.precision} float; 43 | out vec4 outgoingColor; 44 | 45 | void main(void){ 46 | outgoingColor = vec4(1.0); 47 | } 48 | `; 49 | } 50 | 51 | function fragmentShaderEs100() { 52 | return ` 53 | precision ${capabilities.precision} float; 54 | 55 | void main(void){ 56 | gl_FragColor = vec4(1.0); 57 | } 58 | `; 59 | } 60 | 61 | class NormalsGeometry extends Geometry { 62 | constructor(mesh: Mesh, size = 0.5) { 63 | let vertices = []; 64 | 65 | const sx = mesh.scale.x; 66 | const sy = mesh.scale.y; 67 | const sz = mesh.scale.z; 68 | const length = mesh.geometry.bufferNormals.length / 3; 69 | for (let i = 0; i < length; i += 1) { 70 | const i3 = i * 3; 71 | const v0x = sx * mesh.geometry.bufferVertices[i3]; 72 | const v0y = sy * mesh.geometry.bufferVertices[i3 + 1]; 73 | const v0z = sz * mesh.geometry.bufferVertices[i3 + 2]; 74 | const nx = mesh.geometry.bufferNormals[i3]; 75 | const ny = mesh.geometry.bufferNormals[i3 + 1]; 76 | const nz = mesh.geometry.bufferNormals[i3 + 2]; 77 | const v1x = v0x + size * nx; 78 | const v1y = v0y + size * ny; 79 | const v1z = v0z + size * nz; 80 | vertices = vertices.concat([v0x, v0y, v0z, v1x, v1y, v1z]); 81 | } 82 | 83 | super(new Float32Array(vertices)); 84 | } 85 | } 86 | 87 | export default class NormalsHelper extends Mesh { 88 | constructor(mesh, size = 1) { 89 | const vertexShader = GL.webgl2 ? vertexShaderEs300 : vertexShaderEs100; 90 | const fragmentShader = GL.webgl2 91 | ? fragmentShaderEs300() 92 | : fragmentShaderEs100(); 93 | super( 94 | new NormalsGeometry(mesh, size), 95 | new Material({ 96 | name: 'NormalsHelper', 97 | vertexShader, 98 | fragmentShader 99 | }) 100 | ); 101 | } 102 | 103 | public draw(camera: Camera | PerspectiveCamera | OrthographicCamera) { 104 | if (!this.visible) return; 105 | gl = GL.get(); 106 | 107 | // Update modelMatrix 108 | this.updateMatrix(camera); 109 | 110 | this.material.program.bind(); 111 | this.material.setUniforms( 112 | camera.projectionMatrix, 113 | this.modelViewMatrix, 114 | this.modelMatrix, 115 | camera 116 | ); 117 | 118 | if (extensions.vertexArrayObject) { 119 | this.vao.bind(); 120 | } else { 121 | this.bindAttributes(); 122 | this.bindAttributesInstanced(); 123 | this.bindIndexBuffer(); 124 | } 125 | 126 | gl.drawArrays( 127 | gl.LINES, 128 | 0, 129 | this.geometry.attributes.aVertexPosition.numItems 130 | ); 131 | 132 | if (extensions.vertexArrayObject) { 133 | this.vao.unbind(); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/lights/AmbientLight.ts: -------------------------------------------------------------------------------- 1 | import { LIGHT_AMBIENT } from '../core/Constants'; 2 | import * as GL from '../core/GL'; 3 | import Color from '../math/Color'; 4 | import Vector3 from '../math/Vector3'; 5 | import Light from './Light'; 6 | 7 | export default class AmbientLight extends Light { 8 | public uniforms: any; 9 | public position: Vector3; 10 | 11 | constructor(uniforms = {}) { 12 | super(); 13 | this.type = LIGHT_AMBIENT; 14 | this.uniforms = { 15 | color: { 16 | type: '3f', 17 | value: new Color(0x404040).v 18 | }, 19 | intensity: { 20 | type: 'f', 21 | value: 0.1 22 | } 23 | }; 24 | Object.assign(this.uniforms, uniforms); 25 | 26 | if (GL.webgl2) { 27 | // Buffer data 28 | this.data = new Float32Array([ 29 | ...this.uniforms.color.value, 30 | 0.0, 31 | this.uniforms.intensity.value, 32 | 0.0, 33 | 0.0, 34 | 0.0 35 | ]); 36 | } 37 | } 38 | 39 | public update() { 40 | if (GL.webgl2) { 41 | // Set values for buffer data 42 | this.setValues(this.uniforms.color.value, 0); 43 | this.setValues([this.uniforms.intensity.value], 4); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lights/DirectionalLight.ts: -------------------------------------------------------------------------------- 1 | import { LIGHT_DIRECTIONAL } from '../core/Constants'; 2 | import * as GL from '../core/GL'; 3 | import Color from '../math/Color'; 4 | import Vector3 from '../math/Vector3'; 5 | import Light from './Light'; 6 | 7 | export default class DirectionalLight extends Light { 8 | public uniforms: any; 9 | public position: Vector3; 10 | 11 | constructor(uniforms = {}) { 12 | super(); 13 | this.type = LIGHT_DIRECTIONAL; 14 | this.uniforms = { 15 | position: { 16 | type: '3f', 17 | value: new Vector3(0, 0, 0).v 18 | }, 19 | color: { 20 | type: '3f', 21 | value: new Color(0xffffff).v 22 | }, 23 | intensity: { 24 | type: 'f', 25 | value: 1 26 | } 27 | }; 28 | Object.assign(this.uniforms, uniforms); 29 | 30 | this.position = new Vector3(); 31 | 32 | if (GL.webgl2) { 33 | // Buffer data 34 | this.data = new Float32Array([ 35 | ...this.uniforms.position.value, 36 | 0.0, 37 | ...this.uniforms.color.value, 38 | 0.0, 39 | this.uniforms.intensity.value, 40 | 0.0, 41 | 0.0, 42 | 0.0 43 | ]); 44 | } 45 | } 46 | 47 | public update() { 48 | if (GL.webgl2) { 49 | // Set values for buffer data 50 | this.setValues(this.position.v); 51 | this.setValues(this.uniforms.color.value, 4); 52 | this.setValues([this.uniforms.intensity.value], 8); 53 | } else { 54 | this.uniforms.position.value.set(this.position.v); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/lights/Light.ts: -------------------------------------------------------------------------------- 1 | export default class Light { 2 | public type: string; 3 | public data: Float32Array; 4 | 5 | public update() { 6 | return; 7 | } 8 | 9 | public setValues(values: number[] | Float32Array, offset = 0) { 10 | this.data.set(values, offset); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/lights/Lights.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LIGHT_AMBIENT, 3 | LIGHT_DIRECTIONAL, 4 | LIGHT_POINT, 5 | UNIFORM_AMBIENT_LIGHT_LOCATION, 6 | UNIFORM_DIRECTIONAL_LIGHTS_LOCATION, 7 | UNIFORM_POINT_LIGHTS_LOCATION 8 | } from '../core/Constants'; 9 | import * as GL from '../core/GL'; 10 | import UniformBuffer from '../core/UniformBuffer'; 11 | import AmbientLight from '../lights/AmbientLight'; 12 | import DirectionalLight from '../lights/DirectionalLight'; 13 | import PointLight from '../lights/PointLight'; 14 | 15 | let gl; 16 | 17 | /** 18 | * Genetic class for PointLights and DirectionalLights 19 | * Creates a uniform buffer which stores all the data for the specific 20 | * light type 21 | */ 22 | export default class Lights { 23 | public lights: Array; 24 | public uniformBuffer: UniformBuffer; 25 | public _lightsData: Float32Array; 26 | 27 | constructor(lights: Array) { 28 | this.lights = lights; 29 | 30 | gl = GL.get(); 31 | 32 | if (GL.webgl2) { 33 | const dataLength = this.lights[0].data.length; 34 | 35 | // Setup data 36 | const values = Array(lights.length * dataLength); 37 | const data = new Float32Array(values); 38 | 39 | // Create uniform buffer to store all point lights data 40 | // The uniform block is an array of lights 41 | this.uniformBuffer = new UniformBuffer(data); 42 | 43 | // Tmp array for updating the lights data buffer 44 | this._lightsData = new Float32Array( 45 | lights[0].data.length * lights.length 46 | ); 47 | } 48 | } 49 | 50 | get length() { 51 | return this.lights.length; 52 | } 53 | 54 | public get() { 55 | return this.lights; 56 | } 57 | 58 | public update() { 59 | if (GL.webgl2) { 60 | // Get data from lights and update the uniform buffer 61 | this.lights.forEach((light, i) => { 62 | light.update(); 63 | this._lightsData.set(light.data, i * light.data.length); 64 | }); 65 | this.uniformBuffer.setValues(this._lightsData, 0); 66 | } else { 67 | this.lights.forEach(light => { 68 | light.update(); 69 | }); 70 | } 71 | } 72 | 73 | public bind() { 74 | if (GL.webgl2) { 75 | gl = GL.get(); 76 | 77 | gl.bindBuffer(gl.UNIFORM_BUFFER, this.uniformBuffer.buffer); 78 | gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.uniformBuffer.data); 79 | gl.bindBuffer(gl.UNIFORM_BUFFER, null); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/lights/PointLight.ts: -------------------------------------------------------------------------------- 1 | import { LIGHT_POINT } from '../core/Constants'; 2 | import * as GL from '../core/GL'; 3 | import Color from '../math/Color'; 4 | import Vector3 from '../math/Vector3'; 5 | import Light from './Light'; 6 | 7 | export default class PointLight extends Light { 8 | public uniforms: any; 9 | public position: Vector3; 10 | 11 | constructor(uniforms = {}) { 12 | super(); 13 | this.type = LIGHT_POINT; 14 | this.uniforms = { 15 | position: { 16 | type: '3f', 17 | value: new Vector3(0, 0, 0).v 18 | }, 19 | color: { 20 | type: '3f', 21 | value: new Color(0xffffff).v 22 | }, 23 | specularColor: { 24 | type: '3f', 25 | value: new Color(0xffffff).v 26 | }, 27 | shininess: { 28 | type: 'f', 29 | value: 100 30 | }, 31 | intensity: { 32 | type: 'f', 33 | value: 1 34 | } 35 | }; 36 | Object.assign(this.uniforms, uniforms); 37 | 38 | this.position = new Vector3(); 39 | 40 | if (GL.webgl2) { 41 | // Buffer data 42 | this.data = new Float32Array([ 43 | ...this.uniforms.position.value, 44 | 0.0, 45 | ...this.uniforms.color.value, 46 | 0.0, 47 | ...this.uniforms.specularColor.value, 48 | 0.0, 49 | this.uniforms.shininess.value, 50 | 0.0, 51 | 0.0, 52 | 0.0, 53 | this.uniforms.intensity.value, 54 | 0.0, 55 | 0.0, 56 | 0.0 57 | ]); 58 | } 59 | } 60 | 61 | public update() { 62 | if (GL.webgl2) { 63 | // Set values for buffer data 64 | this.setValues(this.position.v); 65 | this.setValues(this.uniforms.color.value, 4); 66 | this.setValues(this.uniforms.specularColor.value, 8); 67 | this.setValues([this.uniforms.shininess.value], 12); 68 | this.setValues([this.uniforms.intensity.value], 16); 69 | } else { 70 | this.uniforms.position.value.set(this.position.v); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/loaders/FileLoader.ts: -------------------------------------------------------------------------------- 1 | import { log } from '../utils/Console'; 2 | export default function FileLoader(url: string, responseType?): Promise { 3 | return new Promise( 4 | (resolve: (response, statis) => void, reject: (status) => void) => { 5 | const req = new XMLHttpRequest(); 6 | req.responseType = responseType || ''; 7 | req.onreadystatechange = () => { 8 | if (req.readyState !== 4) return; 9 | if (req.readyState === 4 && req.status === 200) { 10 | resolve(req.response, req.status); 11 | } else { 12 | reject(req.status); 13 | } 14 | }; 15 | req.open('GET', url, true); 16 | req.send(); 17 | } 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/loaders/HdrLoader.ts: -------------------------------------------------------------------------------- 1 | import parseHDR from 'parse-hdr'; 2 | import ImageData from '../core/ImageData'; 3 | 4 | export default function HdrLoader(src): Promise { 5 | return new Promise((resolve: (image) => void, reject: (status) => void) => { 6 | const req = new XMLHttpRequest(); 7 | req.responseType = 'arraybuffer'; 8 | req.onreadystatechange = () => { 9 | if (req.readyState !== 4) return; 10 | if (req.readyState === 4 && req.status === 200) { 11 | const hdr = parseHDR(req.response); 12 | const image = new ImageData(hdr.shape[0], hdr.shape[1], hdr.data); 13 | resolve(image); 14 | } else { 15 | reject(req.status); 16 | } 17 | }; 18 | req.open('GET', src, true); 19 | req.send(); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/loaders/ImageLoader.ts: -------------------------------------------------------------------------------- 1 | export default function ImageLoader(src): Promise { 2 | return new Promise((resolve: (image) => void, reject: (status) => void) => { 3 | const image = new Image(); 4 | 5 | image.onload = () => { 6 | resolve(image); 7 | }; 8 | 9 | image.onerror = () => { 10 | reject(`Failed to load ${src}`); 11 | }; 12 | 13 | image.src = src; 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/loaders/JsonLoader.ts: -------------------------------------------------------------------------------- 1 | import FileLoader from './FileLoader'; 2 | 3 | export default function(file) { 4 | return new Promise((resolve, reject) => { 5 | FileLoader(file) 6 | .then(response => { 7 | const data = JSON.parse(response); 8 | resolve(data); 9 | }) 10 | .catch(reject); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/loaders/ObjLoader.ts: -------------------------------------------------------------------------------- 1 | import ObjParser from '../utils/ObjParser'; 2 | import FileLoader from './FileLoader'; 3 | 4 | export default function(file) { 5 | return new Promise((resolve, reject) => { 6 | FileLoader(file) 7 | .then(response => { 8 | const data = ObjParser(response); 9 | resolve(data); 10 | }) 11 | .catch(reject); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/math/Color.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from 'gl-matrix'; 2 | 3 | export default class Color { 4 | public v: vec3; 5 | 6 | constructor(hex = 0xffffff) { 7 | this.v = vec3.create(); 8 | this.convert(hex); 9 | return this; 10 | } 11 | set r(value: number) { 12 | this.v[0] = value; 13 | } 14 | get r() { 15 | return this.v[0]; 16 | } 17 | set g(value: number) { 18 | this.v[1] = value; 19 | } 20 | get g() { 21 | return this.v[1]; 22 | } 23 | set b(value: number) { 24 | this.v[2] = value; 25 | } 26 | get b() { 27 | return this.v[2]; 28 | } 29 | public set(r: number, g: number, b: number) { 30 | vec3.set(this.v, r, g, b); 31 | return this; 32 | } 33 | public copy(rgb: number[]) { 34 | vec3.copy(this.v, vec3.fromValues(rgb[0], rgb[1], rgb[2])); 35 | return this; 36 | } 37 | public convert(hex: string | number) { 38 | let rgb; 39 | if (typeof hex === 'number') { 40 | rgb = this.hexIntToRgb(hex); 41 | } 42 | if (typeof hex === 'string') { 43 | rgb = this.hexStringToRgb(hex); 44 | } 45 | vec3.copy(this.v, this.normalize(rgb)); 46 | return this; 47 | } 48 | public normalize(array: number[]) { 49 | return vec3.fromValues(array[0] / 255, array[1] / 255, array[2] / 255); 50 | } 51 | public fromArray(array: number[]) { 52 | this.set(array[0], array[1], array[2]); 53 | } 54 | public componentToHex(c: number) { 55 | const hex = c.toString(16); 56 | return hex.length === 1 ? `0${hex}` : hex; 57 | } 58 | public rgbToHex(r: number, g: number, b: number) { 59 | const hexR = this.componentToHex(r); 60 | const hexG = this.componentToHex(g); 61 | const hexB = this.componentToHex(b); 62 | return `#${hexR}${hexG}${hexB}`; 63 | } 64 | public hexIntToRgb(hex: number) { 65 | const r = hex >> 16; 66 | const g = (hex >> 8) & 0xff; 67 | const b = hex & 0xff; 68 | return vec3.fromValues(r, g, b); 69 | } 70 | public hexStringToRgb(hex: string) { 71 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 72 | return result 73 | ? vec3.fromValues( 74 | parseInt(result[1], 16), 75 | parseInt(result[2], 16), 76 | parseInt(result[3], 16) 77 | ) 78 | : null; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/math/Ray.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from 'gl-matrix'; 2 | import Sphere from './Sphere'; 3 | import Vector3 from './Vector3'; 4 | 5 | const diff = vec3.create(); 6 | const edge1 = vec3.create(); 7 | const edge2 = vec3.create(); 8 | const normal = vec3.create(); 9 | const v1 = vec3.create(); 10 | 11 | export default class Ray { 12 | public origin: Vector3; 13 | public direction: Vector3; 14 | 15 | constructor() { 16 | this.origin = new Vector3(); 17 | this.direction = new Vector3(); 18 | } 19 | 20 | public set(origin: Vector3, direction: Vector3) { 21 | this.origin.copy(origin); 22 | this.direction.copy(direction); 23 | } 24 | 25 | public intersectTriangle(a: Vector3, b: Vector3, c: Vector3, culling = true) { 26 | vec3.sub(edge1, b.v, a.v); 27 | vec3.sub(edge2, c.v, a.v); 28 | vec3.cross(normal, edge1, edge2); 29 | 30 | // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, 31 | // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by 32 | // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) 33 | // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) 34 | // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) 35 | 36 | // console.log('normal', normal); 37 | let DdN = vec3.dot(this.direction.v, normal); 38 | let sign; 39 | 40 | // console.log('normal', normal); 41 | 42 | if (DdN > 0) { 43 | if (culling) return null; 44 | sign = 1; 45 | } else if (DdN < 0) { 46 | sign = -1; 47 | DdN = -DdN; 48 | } else { 49 | return null; 50 | } 51 | 52 | vec3.sub(diff, this.origin.v, a.v); 53 | vec3.cross(edge2, diff, edge2); 54 | const DdQxE2 = sign * vec3.dot(this.direction.v, edge2); 55 | 56 | // b1 < 0, no intersection 57 | if (DdQxE2 < 0) { 58 | return null; 59 | } 60 | 61 | vec3.cross(edge1, edge1, diff); 62 | const DdE1xQ = sign * vec3.dot(this.direction.v, edge1); 63 | 64 | // b2 < 0, no intersection 65 | if (DdE1xQ < 0) { 66 | return null; 67 | } 68 | 69 | // b1+b2 > 1, no intersection 70 | if (DdQxE2 + DdE1xQ > DdN) { 71 | return null; 72 | } 73 | 74 | // Line intersects triangle, check if ray does. 75 | const QdN = -sign * vec3.dot(diff, normal); 76 | 77 | // t < 0, no intersection 78 | if (QdN < 0) { 79 | return null; 80 | } 81 | 82 | const result = new Vector3(); 83 | result 84 | .copy(this.direction) 85 | .scale(QdN / DdN) 86 | .add(this.origin); 87 | 88 | return result; 89 | } 90 | 91 | public at(scale: number) { 92 | const result = vec3.fromValues( 93 | this.direction.v[0], 94 | this.direction.v[1], 95 | this.direction.v[2] 96 | ); 97 | vec3.scale(result, result, scale); 98 | vec3.add(result, result, this.origin.v); 99 | } 100 | 101 | public intersectsSphere(sphere) { 102 | return this.distanceToPoint(sphere.center) <= sphere.radius; 103 | } 104 | 105 | public distanceToPoint(point) { 106 | return Math.sqrt(this.distanceSqToPoint(point)); 107 | } 108 | 109 | public distanceSqToPoint(point: Vector3) { 110 | vec3.subtract(v1, point.v, this.origin.v); 111 | const directionDistance = vec3.dot(v1, this.direction.v); 112 | 113 | // point behind the ray 114 | if (directionDistance < 0) { 115 | return vec3.squaredDistance(this.origin.v, point.v); 116 | } 117 | 118 | vec3.copy(v1, this.direction.v); 119 | vec3.scale(v1, v1, directionDistance); 120 | vec3.add(v1, v1, this.origin.v); 121 | 122 | return vec3.squaredDistance(v1, point.v); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/math/Sphere.ts: -------------------------------------------------------------------------------- 1 | import { mat4, vec3 } from 'gl-matrix'; 2 | import Vector3 from './Vector3'; 3 | 4 | export default class Sphere { 5 | public center: Vector3; 6 | public radius: number; 7 | 8 | constructor(center: Vector3 = new Vector3(), radius: number = 0) { 9 | this.center = center; 10 | this.radius = radius; 11 | } 12 | 13 | public copy(sphere: Sphere) { 14 | this.center.copy(sphere.center); 15 | this.radius = sphere.radius; 16 | } 17 | 18 | public applyMatrix(matrix: mat4) { 19 | vec3.transformMat4(this.center.v, this.center.v, matrix); 20 | const scaling = mat4.getScaling(vec3.create(), matrix); 21 | this.radius *= Math.max(scaling[0], scaling[1], scaling[2]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/math/Utils.ts: -------------------------------------------------------------------------------- 1 | import { mat4, quat, vec3 } from 'gl-matrix'; 2 | import Vector2 from '../math/Vector2'; 3 | import Vector3 from '../math/Vector3'; 4 | 5 | export function degToRad(degrees: number) { 6 | return degrees * (Math.PI / 180); 7 | } 8 | 9 | export function radToDeg(radians: number) { 10 | return radians * (180 / Math.PI); 11 | } 12 | 13 | export function clamp(value: number, min: number, max: number) { 14 | return Math.max(Math.min(value, max), min); 15 | } 16 | 17 | export function lerp(min: number, max: number, alpha: number) { 18 | return min + (max - min) * alpha; 19 | } 20 | 21 | export function barycoordFromPoint(point: vec3, a: vec3, b: vec3, c: vec3) { 22 | const v0 = vec3.create(); 23 | const v1 = vec3.create(); 24 | const v2 = vec3.create(); 25 | 26 | vec3.sub(v0, c, a); 27 | vec3.sub(v1, b, a); 28 | vec3.sub(v2, point, a); 29 | 30 | const dot00 = vec3.dot(v0, v0); 31 | const dot01 = vec3.dot(v0, v1); 32 | const dot02 = vec3.dot(v0, v2); 33 | const dot11 = vec3.dot(v1, v1); 34 | const dot12 = vec3.dot(v1, v2); 35 | 36 | const denom = dot00 * dot11 - dot01 * dot01; 37 | 38 | const result = new Vector3(); 39 | 40 | // collinear or singular triangle 41 | if (denom === 0) { 42 | // arbitrary location outside of triangle? 43 | // not sure if this is the best idea, maybe should be returning undefined 44 | return result.set(-2, -1, -1); 45 | } 46 | 47 | const invDenom = 1 / denom; 48 | const u = (dot11 * dot02 - dot01 * dot12) * invDenom; 49 | const v = (dot00 * dot12 - dot01 * dot02) * invDenom; 50 | 51 | // barycentric coordinates must always sum to 1 52 | return result.set(1 - u - v, v, u); 53 | } 54 | 55 | /* 56 | http://stackoverflow.com/questions/5531827/random-point-on-a-given-sphere 57 | */ 58 | export function randomSpherePoint( 59 | x0: number, 60 | y0: number, 61 | z0: number, 62 | radius: number 63 | ) { 64 | const u = Math.random(); 65 | const v = Math.random(); 66 | const theta = 2 * Math.PI * u; 67 | const phi = Math.acos(2 * v - 1); 68 | const x = x0 + radius * Math.sin(phi) * Math.cos(theta); 69 | const y = y0 + radius * Math.sin(phi) * Math.sin(theta); 70 | const z = z0 + radius * Math.cos(phi); 71 | return [x, y, z]; 72 | } 73 | 74 | // https://github.com/hughsk/from-3d-to-2d/blob/master/index.js 75 | export function from3DTo2D(position: Vector3, pVMatrix: mat4) { 76 | const ix = position.x; 77 | const iy = position.y; 78 | const iz = position.z; 79 | 80 | const ox = 81 | pVMatrix[0] * ix + pVMatrix[4] * iy + pVMatrix[8] * iz + pVMatrix[12]; 82 | const oy = 83 | pVMatrix[1] * ix + pVMatrix[5] * iy + pVMatrix[9] * iz + pVMatrix[13]; 84 | const ow = 85 | pVMatrix[3] * ix + pVMatrix[7] * iy + pVMatrix[11] * iz + pVMatrix[15]; 86 | 87 | const screenPosition = new Vector2(); 88 | screenPosition.x = (ox / ow + 1) / 2; 89 | screenPosition.y = 1 - (oy / ow + 1) / 2; 90 | 91 | return screenPosition; 92 | } 93 | 94 | // https://github.com/mrdoob/three.js/blob/master/src/math/Matrix4.js#L324 95 | export function lookAt(eye: vec3, target: vec3, up: vec3) { 96 | const quatOut = quat.create(); 97 | const x = vec3.create(); 98 | const y = vec3.create(); 99 | const z = vec3.create(); 100 | 101 | vec3.sub(z, eye, target); 102 | 103 | if (vec3.squaredLength(z) === 0) { 104 | // eye and target are in the same position 105 | z[2] = 1; 106 | } 107 | 108 | vec3.normalize(z, z); 109 | vec3.cross(x, up, z); 110 | 111 | if (vec3.squaredLength(x) === 0) { 112 | // eye and target are in the same vertical 113 | z[2] += 0.0001; 114 | vec3.cross(x, up, z); 115 | } 116 | 117 | vec3.normalize(x, x); 118 | vec3.cross(y, z, x); 119 | 120 | quat.setAxes(quatOut, z, x, y); 121 | quat.invert(quatOut, quatOut); 122 | 123 | return quatOut; 124 | } 125 | 126 | // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL 127 | export function isPowerOf2(value) { 128 | return (value & (value - 1)) === 0; 129 | } 130 | 131 | // https://bocoup.com/blog/find-the-closest-power-of-2-with-javascript 132 | export function nearestPowerOf2(size) { 133 | return Math.pow(2, Math.round(Math.log(size) / Math.log(2))); 134 | } 135 | 136 | export function addLineNumbers(text: string) { 137 | let result = ''; 138 | text.split('\n').forEach((line: string, index: number) => { 139 | result += `${index < 10 ? `0${index}` : index}| ${line}\n`; 140 | }); 141 | return result; 142 | } 143 | -------------------------------------------------------------------------------- /src/math/Vector2.ts: -------------------------------------------------------------------------------- 1 | import { vec2 } from 'gl-matrix'; 2 | 3 | export default class Vector2 { 4 | public v: vec2; 5 | 6 | constructor(x = 0, y = 0) { 7 | this.v = vec2.create(); 8 | this.set(x, y); 9 | return this; 10 | } 11 | set x(value: number) { 12 | this.v[0] = value; 13 | } 14 | get x() { 15 | return this.v[0]; 16 | } 17 | set y(value: number) { 18 | this.v[1] = value; 19 | } 20 | get y() { 21 | return this.v[1]; 22 | } 23 | public set(x: number, y: number) { 24 | vec2.set(this.v, x, y); 25 | return this; 26 | } 27 | public clone() { 28 | return new Vector2(this.v[0], this.v[1]); 29 | } 30 | public copy(vector2: Vector2) { 31 | vec2.copy(this.v, vector2.v); 32 | return this; 33 | } 34 | public add(vector2: Vector2) { 35 | vec2.add(this.v, this.v, vector2.v); 36 | return this; 37 | } 38 | public subtract(vector2: Vector2) { 39 | vec2.subtract(this.v, this.v, vector2.v); 40 | return this; 41 | } 42 | public subtractVectors(vector0: Vector2, vector1: Vector2) { 43 | const out = vec2.create(); 44 | vec2.subtract(out, vector0.v, vector1.v); 45 | return out; 46 | } 47 | public scale(value: number) { 48 | vec2.scale(this.v, this.v, value); 49 | return this; 50 | } 51 | public distance(vector2: Vector2) { 52 | return vec2.distance(this.v, vector2.v); 53 | } 54 | public length(): number { 55 | return vec2.length(this.v); 56 | } 57 | public negate() { 58 | vec2.negate(this.v, this.v); 59 | return this; 60 | } 61 | public normalize() { 62 | vec2.normalize(this.v, this.v); 63 | return this; 64 | } 65 | public lerp(vector2: Vector2, value: number) { 66 | vec2.lerp(this.v, this.v, vector2.v, value); 67 | return this; 68 | } 69 | public equals(vector2: Vector2): boolean { 70 | return vec2.equals(this.v, vector2.v); 71 | } 72 | public multiply(vector2: Vector2) { 73 | this.v[0] *= vector2.v[0]; 74 | this.v[1] *= vector2.v[1]; 75 | return this; 76 | } 77 | public fromArray(values: number[]) { 78 | return vec2.copy(this.v, values); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/math/Vector3.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from 'gl-matrix'; 2 | 3 | export default class Vector3 { 4 | public static up = new Vector3(0, 1, 0); 5 | public v: vec3; 6 | constructor(x = 0, y = 0, z = 0) { 7 | this.v = vec3.create(); 8 | this.set(x, y, z); 9 | return this; 10 | } 11 | set x(value: number) { 12 | this.v[0] = value; 13 | } 14 | get x() { 15 | return this.v[0]; 16 | } 17 | set y(value: number) { 18 | this.v[1] = value; 19 | } 20 | get y() { 21 | return this.v[1]; 22 | } 23 | set z(value: number) { 24 | this.v[2] = value; 25 | } 26 | get z() { 27 | return this.v[2]; 28 | } 29 | public set(x: number, y: number, z: number) { 30 | vec3.set(this.v, x, y, z); 31 | return this; 32 | } 33 | public clone() { 34 | return new Vector3(this.v[0], this.v[1], this.v[2]); 35 | } 36 | public copy(vector3: Vector3) { 37 | vec3.copy(this.v, vector3.v); 38 | return this; 39 | } 40 | public add(vector3: Vector3) { 41 | vec3.add(this.v, this.v, vector3.v); 42 | return this; 43 | } 44 | public subtract(vector3: Vector3) { 45 | vec3.subtract(this.v, this.v, vector3.v); 46 | return this; 47 | } 48 | public subtractVectors(vector0: Vector3, vector1: Vector3) { 49 | const out = vec3.create(); 50 | vec3.subtract(out, vector0.v, vector1.v); 51 | return out; 52 | } 53 | public scale(value: number) { 54 | vec3.scale(this.v, this.v, value); 55 | return this; 56 | } 57 | public distance(vector3: Vector3) { 58 | return vec3.distance(this.v, vector3.v); 59 | } 60 | public length(): number { 61 | return vec3.length(this.v); 62 | } 63 | public negate() { 64 | vec3.negate(this.v, this.v); 65 | return this; 66 | } 67 | public normalize() { 68 | vec3.normalize(this.v, this.v); 69 | return this; 70 | } 71 | public dot(vector3: Vector3): number { 72 | return vec3.dot(this.v, vector3.v); 73 | } 74 | public cross(vector3: Vector3) { 75 | vec3.cross(this.v, this.v, vector3.v); 76 | return this; 77 | } 78 | public crossVectors(vector0: Vector3, vector1: Vector3) { 79 | const out = vec3.create(); 80 | vec3.cross(out, vector0.v, vector1.v); 81 | return out; 82 | } 83 | public lerp(vector3: Vector3, value: number) { 84 | vec3.lerp(this.v, this.v, vector3.v, value); 85 | return this; 86 | } 87 | public equals(vector3: Vector3): boolean { 88 | return vec3.equals(this.v, vector3.v); 89 | } 90 | public multiply(vector3: Vector3) { 91 | this.v[0] *= vector3.v[0]; 92 | this.v[1] *= vector3.v[1]; 93 | this.v[2] *= vector3.v[2]; 94 | return this; 95 | } 96 | public fromArray(values: number[]) { 97 | return vec3.copy(this.v, values); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/shaders/Basic.glsl.ts: -------------------------------------------------------------------------------- 1 | import EsVersion from './chunks/EsVersion.glsl'; 2 | 3 | const basicFragmentShaderEs300 = `${EsVersion} 4 | #HOOK_PRECISION 5 | #HOOK_DEFINES 6 | 7 | layout(std140) uniform; 8 | 9 | in vec3 vDiffuse; 10 | in vec3 vPosition; 11 | 12 | #ifdef normals 13 | in vec3 vNormal; 14 | #endif 15 | 16 | #ifdef uv 17 | in vec2 vUv; 18 | #endif 19 | 20 | out vec4 outgoingColor; 21 | 22 | #HOOK_FRAGMENT_PRE 23 | 24 | void main(void){ 25 | 26 | vec3 color = vDiffuse; 27 | 28 | #ifdef normals 29 | vec3 normal = normalize(vNormal); 30 | #endif 31 | 32 | #HOOK_FRAGMENT_MAIN 33 | 34 | outgoingColor = vec4(color.rgb, 1.0); 35 | 36 | #HOOK_FRAGMENT_END 37 | } 38 | `; 39 | 40 | const basicFragmentShaderEs100 = ` 41 | #HOOK_PRECISION 42 | #HOOK_DEFINES 43 | 44 | varying vec3 vDiffuse; 45 | varying vec3 vPosition; 46 | 47 | #ifdef normals 48 | varying vec3 vNormal; 49 | #endif 50 | 51 | #ifdef uv 52 | varying vec2 vUv; 53 | #endif 54 | 55 | #HOOK_FRAGMENT_PRE 56 | 57 | void main(void){ 58 | 59 | vec3 color = vDiffuse; 60 | 61 | #ifdef normals 62 | vec3 normal = normalize(vNormal); 63 | #endif 64 | 65 | #HOOK_FRAGMENT_MAIN 66 | 67 | gl_FragColor = vec4(color.rgb, 1.0); 68 | 69 | #HOOK_FRAGMENT_END 70 | } 71 | `; 72 | 73 | export { basicFragmentShaderEs300, basicFragmentShaderEs100 }; 74 | -------------------------------------------------------------------------------- /src/shaders/Lambert.glsl.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ambientLightEs100, 3 | ambientLightEs300 4 | } from './chunks/AmbientLight.glsl'; 5 | import { 6 | directionalLightsEs100, 7 | directionalLightsEs300 8 | } from './chunks/DirectionalLights.glsl'; 9 | import EsVersion from './chunks/EsVersion.glsl'; 10 | import { lambertEs100, lambertEs300 } from './chunks/Lambert.glsl'; 11 | 12 | const lambertFragmentShaderEs300 = `${EsVersion} 13 | #HOOK_PRECISION 14 | #HOOK_DEFINES 15 | 16 | layout(std140) uniform; 17 | 18 | in vec3 vDiffuse; 19 | in vec3 vPosition; 20 | 21 | #ifdef normals 22 | in vec3 vNormal; 23 | #endif 24 | 25 | #ifdef uv 26 | in vec2 vUv; 27 | #endif 28 | 29 | #ifdef ambientLight 30 | ${ambientLightEs300} 31 | #endif 32 | 33 | #ifdef directionalLights 34 | ${directionalLightsEs300} 35 | ${lambertEs300} 36 | #endif 37 | 38 | out vec4 outgoingColor; 39 | 40 | 41 | #HOOK_FRAGMENT_PRE 42 | 43 | void main(void){ 44 | 45 | vec3 color = vec3(0.0); 46 | 47 | #ifdef normals 48 | vec3 normal = normalize(vNormal); 49 | #endif 50 | 51 | #HOOK_FRAGMENT_MAIN 52 | 53 | #ifdef directionalLights 54 | for (int i = 0; i < #HOOK_DIRECTIONAL_LIGHTS; i++) { 55 | color += CalculateDirectionalLight(uDirectionalLights[i], normal); 56 | } 57 | #endif 58 | 59 | outgoingColor = vec4(color.rgb, 1.0); 60 | 61 | #HOOK_FRAGMENT_END 62 | } 63 | `; 64 | 65 | const lambertFragmentShaderEs100 = ` 66 | #HOOK_PRECISION 67 | #HOOK_DEFINES 68 | 69 | varying vec3 vDiffuse; 70 | varying vec3 vPosition; 71 | 72 | #ifdef normals 73 | varying vec3 vNormal; 74 | #endif 75 | 76 | #ifdef uv 77 | varying vec2 vUv; 78 | #endif 79 | 80 | #ifdef ambientLight 81 | ${ambientLightEs100} 82 | #endif 83 | 84 | #ifdef directionalLights 85 | ${directionalLightsEs100} 86 | ${lambertEs100} 87 | #endif 88 | 89 | #HOOK_FRAGMENT_PRE 90 | 91 | void main(void){ 92 | 93 | vec3 color = vec3(0.0); 94 | 95 | #ifdef normals 96 | vec3 normal = normalize(vNormal); 97 | #endif 98 | 99 | #HOOK_FRAGMENT_MAIN 100 | 101 | #ifdef directionalLights 102 | for (int i = 0; i < #HOOK_DIRECTIONAL_LIGHTS; i++) { 103 | color += CalculateDirectionalLight(uDirectionalLights[i], normal); 104 | } 105 | #endif 106 | 107 | gl_FragColor = vec4(color.rgb, 1.0); 108 | 109 | #HOOK_FRAGMENT_END 110 | } 111 | `; 112 | 113 | export { lambertFragmentShaderEs300, lambertFragmentShaderEs100 }; 114 | -------------------------------------------------------------------------------- /src/shaders/Phong.glsl.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ambientLightEs100, 3 | ambientLightEs300 4 | } from './chunks/AmbientLight.glsl'; 5 | import { 6 | directionalLightsEs100, 7 | directionalLightsEs300 8 | } from './chunks/DirectionalLights.glsl'; 9 | import EsVersion from './chunks/EsVersion.glsl'; 10 | import { lambertEs100, lambertEs300 } from './chunks/Lambert.glsl'; 11 | import { phongEs100, phongEs300 } from './chunks/Phong.glsl'; 12 | import { pointLightsEs100, pointLightsEs300 } from './chunks/PointLights.glsl'; 13 | 14 | const phongFragmentShaderEs300 = `${EsVersion} 15 | #HOOK_PRECISION 16 | #HOOK_DEFINES 17 | 18 | layout(std140) uniform; 19 | 20 | in vec3 vDiffuse; 21 | in vec3 vPosition; 22 | in vec4 vWorldPosition; 23 | 24 | uniform vec3 uCameraPosition; 25 | 26 | #ifdef normals 27 | in vec3 vNormal; 28 | #endif 29 | 30 | #ifdef uv 31 | in vec2 vUv; 32 | #endif 33 | 34 | #ifdef ambientLight 35 | ${ambientLightEs300} 36 | #endif 37 | 38 | #ifdef directionalLights 39 | ${directionalLightsEs300} 40 | ${lambertEs300} 41 | #endif 42 | 43 | #ifdef pointLights 44 | ${pointLightsEs300} 45 | ${phongEs300} 46 | #endif 47 | 48 | out vec4 outgoingColor; 49 | 50 | #HOOK_FRAGMENT_PRE 51 | 52 | void main(void){ 53 | 54 | vec3 color = vec3(0.0); 55 | 56 | #ifdef normals 57 | vec3 normal = normalize(vNormal); 58 | #endif 59 | 60 | #HOOK_FRAGMENT_MAIN 61 | 62 | #ifdef directionalLights 63 | for (int i = 0; i < #HOOK_DIRECTIONAL_LIGHTS; i++) { 64 | color += CalculateDirectionalLight(uDirectionalLights[i], normal); 65 | } 66 | #endif 67 | 68 | #ifdef pointLights 69 | for (int i = 0; i < #HOOK_POINT_LIGHTS; i++) { 70 | color += CalculatePointLight(uPointLights[i], normal); 71 | } 72 | #endif 73 | 74 | outgoingColor = vec4(color.rgb, 1.0); 75 | 76 | #HOOK_FRAGMENT_END 77 | } 78 | `; 79 | 80 | const phongFragmentShaderEs100 = ` 81 | #HOOK_PRECISION 82 | #HOOK_DEFINES 83 | 84 | varying vec3 vDiffuse; 85 | varying vec3 vPosition; 86 | varying vec4 vWorldPosition; 87 | 88 | #ifdef normals 89 | varying vec3 vNormal; 90 | #endif 91 | 92 | #ifdef uv 93 | varying vec2 vUv; 94 | #endif 95 | 96 | #ifdef ambientLight 97 | ${ambientLightEs100} 98 | #endif 99 | 100 | #ifdef directionalLights 101 | ${directionalLightsEs100} 102 | ${lambertEs100} 103 | #endif 104 | 105 | #ifdef pointLights 106 | ${pointLightsEs100} 107 | ${phongEs100} 108 | #endif 109 | 110 | #HOOK_FRAGMENT_PRE 111 | 112 | void main(void){ 113 | 114 | vec3 color = vec3(0.0); 115 | 116 | #ifdef normals 117 | vec3 normal = normalize(vNormal); 118 | #endif 119 | 120 | #HOOK_FRAGMENT_MAIN 121 | 122 | #ifdef directionalLights 123 | for (int i = 0; i < #HOOK_DIRECTIONAL_LIGHTS; i++) { 124 | color += CalculateDirectionalLight(uDirectionalLights[i], normal); 125 | } 126 | #endif 127 | 128 | #ifdef pointLights 129 | for (int i = 0; i < #HOOK_POINT_LIGHTS; i++) { 130 | color += CalculatePointLight(uPointLights[i], normal); 131 | } 132 | #endif 133 | 134 | gl_FragColor = vec4(color.rgb, 1.0); 135 | 136 | #HOOK_FRAGMENT_END 137 | } 138 | `; 139 | 140 | export { phongFragmentShaderEs300, phongFragmentShaderEs100 }; 141 | -------------------------------------------------------------------------------- /src/shaders/Vertex.glsl.ts: -------------------------------------------------------------------------------- 1 | import EsVersion from './chunks/EsVersion.glsl'; 2 | import { definePI, definePITwo } from './chunks/Math.glsl'; 3 | import ProjectionView from './chunks/ProjectionView.glsl'; 4 | 5 | const vertexShaderEs300 = `${EsVersion} 6 | ${definePI} 7 | ${definePITwo} 8 | #HOOK_DEFINES 9 | 10 | layout(std140) uniform; 11 | 12 | ${ProjectionView} 13 | 14 | uniform mat4 uProjectionMatrix; 15 | uniform mat4 uModelViewMatrix; 16 | uniform mat4 uModelMatrix; 17 | uniform mat3 uNormalMatrix; 18 | 19 | in vec3 aVertexPosition; 20 | out vec3 vPosition; 21 | out vec4 vWorldPosition; 22 | 23 | // Camera 24 | uniform vec3 uCameraPosition; 25 | 26 | #ifdef vertexColors 27 | in vec3 aVertexColor; 28 | #endif 29 | 30 | // Color 31 | uniform vec3 uDiffuse; 32 | out vec3 vDiffuse; 33 | 34 | // Normal 35 | #ifdef normals 36 | in vec3 aVertexNormal; 37 | out vec3 vNormal; 38 | #endif 39 | 40 | // Uv 41 | #ifdef uv 42 | in vec2 aUv; 43 | out vec2 vUv; 44 | #endif 45 | 46 | #HOOK_VERTEX_PRE 47 | 48 | void main(void){ 49 | 50 | vDiffuse = uDiffuse; 51 | 52 | // Override for custom positioning 53 | vec3 transformed = vec3(0.0); 54 | 55 | #ifdef vertexColors 56 | vDiffuse = aVertexColor; 57 | #endif 58 | 59 | #ifdef uv 60 | vUv = aUv; 61 | #endif 62 | 63 | #HOOK_VERTEX_MAIN 64 | 65 | #ifdef normals 66 | vNormal = uNormalMatrix * aVertexNormal; 67 | #endif 68 | 69 | // Vertex position + offset 70 | vPosition = aVertexPosition + transformed; 71 | 72 | // Calculate world position of vertex with transformed 73 | vWorldPosition = uModelMatrix * vec4(aVertexPosition + transformed, 1.0); 74 | 75 | gl_Position = uProjectionView.projectionMatrix * uModelViewMatrix * vec4(vPosition, 1.0); 76 | 77 | #HOOK_VERTEX_END 78 | } 79 | `; 80 | 81 | const vertexShaderEs100 = ` 82 | 83 | ${definePI} 84 | ${definePITwo} 85 | #HOOK_DEFINES 86 | 87 | // Position 88 | uniform mat4 uProjectionMatrix; 89 | uniform mat4 uModelViewMatrix; 90 | uniform mat4 uModelMatrix; 91 | uniform mat3 uNormalMatrix; 92 | attribute vec3 aVertexPosition; 93 | varying vec3 vPosition; 94 | varying vec4 vWorldPosition; 95 | 96 | // Camera 97 | uniform vec3 uCameraPosition; 98 | 99 | #ifdef vertexColors 100 | attribute vec3 aVertexColor; 101 | #endif 102 | 103 | // Color 104 | uniform vec3 uDiffuse; 105 | varying vec3 vDiffuse; 106 | 107 | // Normal 108 | #ifdef normals 109 | attribute vec3 aVertexNormal; 110 | varying vec3 vNormal; 111 | #endif 112 | 113 | // Uv 114 | #ifdef uv 115 | attribute vec2 aUv; 116 | varying vec2 vUv; 117 | #endif 118 | 119 | #HOOK_VERTEX_PRE 120 | 121 | void main(void){ 122 | 123 | vDiffuse = uDiffuse; 124 | 125 | // Override for custom positioning 126 | vec3 transformed = vec3(0.0); 127 | 128 | #ifdef vertexColors 129 | vDiffuse = aVertexColor; 130 | #endif 131 | 132 | #ifdef uv 133 | vUv = aUv; 134 | #endif 135 | 136 | #HOOK_VERTEX_MAIN 137 | 138 | #ifdef normals 139 | vNormal = uNormalMatrix * aVertexNormal; 140 | #endif 141 | 142 | // Vertex position + offset 143 | vPosition = aVertexPosition + transformed; 144 | 145 | // Calculate world position of vertex with transformed 146 | vWorldPosition = uModelMatrix * vec4(aVertexPosition + transformed, 1.0); 147 | 148 | gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(vPosition, 1.0); 149 | 150 | #HOOK_VERTEX_END 151 | } 152 | `; 153 | 154 | export { vertexShaderEs300, vertexShaderEs100 }; 155 | -------------------------------------------------------------------------------- /src/shaders/chunks/AmbientLight.glsl.ts: -------------------------------------------------------------------------------- 1 | const ambientLightEs300 = ` 2 | uniform AmbientLight { 3 | vec4 color; 4 | vec4 intensity; 5 | } uAmbientLight; 6 | `; 7 | 8 | const ambientLightEs100 = ` 9 | struct AmbientLight { 10 | vec3 color; 11 | float intensity; 12 | }; 13 | uniform AmbientLight uAmbientLight; 14 | `; 15 | 16 | export { ambientLightEs300, ambientLightEs100 }; 17 | -------------------------------------------------------------------------------- /src/shaders/chunks/Conditionals.glsl.ts: -------------------------------------------------------------------------------- 1 | export const whenEquals = ` 2 | float whenEquals(float x, float y) { 3 | return 1.0 - abs(sign(x - y)); 4 | }; 5 | `; 6 | 7 | export const whenEqualsInt = ` 8 | int whenEqualsInt(int x, int y) { 9 | return 1 - abs(sign(x - y)); 10 | } 11 | `; 12 | 13 | export const whenLessThan = ` 14 | float whenLessThan(float x, float y) { 15 | return max(sign(y - x), 0.0); 16 | } 17 | `; 18 | 19 | export const whenGreaterThan = ` 20 | float whenGreaterThan(float x, float y) { 21 | return max(sign(x - y), 0.0); 22 | } 23 | `; 24 | 25 | export default { 26 | whenEquals, 27 | whenEqualsInt, 28 | whenLessThan, 29 | whenGreaterThan 30 | }; 31 | -------------------------------------------------------------------------------- /src/shaders/chunks/DirectionalLights.glsl.ts: -------------------------------------------------------------------------------- 1 | const directionalLightsEs300 = ` 2 | struct DirectionalLight { 3 | vec4 position; 4 | vec4 color; 5 | vec4 intensity; 6 | }; 7 | uniform DirectionalLights { 8 | DirectionalLight uDirectionalLights[#HOOK_DIRECTIONAL_LIGHTS]; 9 | }; 10 | `; 11 | 12 | const directionalLightsEs100 = ` 13 | struct DirectionalLight { 14 | vec3 position; 15 | vec3 color; 16 | float intensity; 17 | }; 18 | uniform DirectionalLight uDirectionalLights[#HOOK_DIRECTIONAL_LIGHTS]; 19 | `; 20 | 21 | export { directionalLightsEs300, directionalLightsEs100 }; 22 | -------------------------------------------------------------------------------- /src/shaders/chunks/EnvMapCube.glsl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/vorg/pragmatic-pbr/blob/master/local_modules/glsl-envmap-cube/index.glsl 3 | */ 4 | 5 | export default ` 6 | /** 7 | * Samples cubemap environment map 8 | * @param {vec3} wcNormal - normal in the world coordinate space 9 | * @param {float} - flipEnvMap -1.0 for left handed coorinate system oriented texture (usual case) 10 | * 1.0 for right handed coorinate system oriented texture 11 | * @return {vec4} - cubemap texture coordinate 12 | */ 13 | vec3 envMapCube(vec3 wcNormal, float flipEnvMap) { 14 | return vec3(flipEnvMap * wcNormal.x, wcNormal.y, wcNormal.z); 15 | } 16 | 17 | vec3 envMapCube(vec3 wcNormal) { 18 | //-1.0 for left handed coorinate system oriented texture (usual case) 19 | return envMapCube(wcNormal, -1.0); 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /src/shaders/chunks/EsVersion.glsl.ts: -------------------------------------------------------------------------------- 1 | // Es300 2 | export default '#version 300 es'; 3 | -------------------------------------------------------------------------------- /src/shaders/chunks/Fog.glsl.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/hughsk/glsl-fog 2 | 3 | export const linear = ` 4 | float fogLinear(const float dist, const float start, const float end) { 5 | return 1.0 - clamp((end - dist) / (end - start), 0.0, 1.0); 6 | } 7 | `; 8 | 9 | export const exp = ` 10 | float fogExp( 11 | const float dist, 12 | const float density 13 | ) { 14 | return 1.0 - clamp(exp(-density * dist), 0.0, 1.0); 15 | } 16 | `; 17 | 18 | export const exp2 = ` 19 | float fogExp2( 20 | const float dist, 21 | const float density 22 | ) { 23 | const float LOG2 = -1.442695; 24 | float d = density * dist; 25 | return 1.0 - clamp(exp2(d * d * LOG2), 0.0, 1.0); 26 | } 27 | `; 28 | 29 | export default { 30 | linear, 31 | exp, 32 | exp2 33 | }; 34 | -------------------------------------------------------------------------------- /src/shaders/chunks/Gamma.glsl.ts: -------------------------------------------------------------------------------- 1 | // https://raw.githubusercontent.com/stackgl/glsl-gamma/master/out.glsl 2 | 3 | export default ` 4 | const float gamma = 2.2; 5 | 6 | float toGamma(float v) { 7 | return pow(v, 1.0 / gamma); 8 | } 9 | 10 | vec2 toGamma(vec2 v) { 11 | return pow(v, vec2(1.0 / gamma)); 12 | } 13 | 14 | vec3 toGamma(vec3 v) { 15 | return pow(v, vec3(1.0 / gamma)); 16 | } 17 | 18 | vec4 toGamma(vec4 v) { 19 | return vec4(toGamma(v.rgb), v.a); 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /src/shaders/chunks/Lambert.glsl.ts: -------------------------------------------------------------------------------- 1 | const lambertEs300 = ` 2 | vec3 CalculateDirectionalLight(DirectionalLight light, vec3 normal) { 3 | vec3 lightDirection = normalize(light.position.xyz); 4 | // diffuse shading 5 | float diff = max(dot(lightDirection, normal), 0.0); 6 | 7 | vec3 ambientColor = vec3(0.5); 8 | float ambientIntensity = 0.5; 9 | 10 | #ifdef ambientLight 11 | ambientColor = uAmbientLight.color.rgb; 12 | ambientIntensity = uAmbientLight.intensity.x; 13 | #endif 14 | 15 | // combine results 16 | vec3 ambient = (ambientColor * ambientIntensity) * vDiffuse; 17 | vec3 diffuse = light.color.rgb * diff * vDiffuse; 18 | return (ambient + diffuse * light.intensity.x); 19 | } 20 | `; 21 | 22 | const lambertEs100 = ` 23 | vec3 CalculateDirectionalLight(DirectionalLight light, vec3 normal) { 24 | vec3 lightDirection = normalize(light.position); 25 | // diffuse shading 26 | float diff = max(dot(lightDirection, normal), 0.0); 27 | 28 | vec3 ambientColor = vec3(0.5); 29 | float ambientIntensity = 0.5; 30 | 31 | #ifdef ambientLight 32 | ambientColor = uAmbientLight.color; 33 | ambientIntensity = uAmbientLight.intensity; 34 | #endif 35 | 36 | // combine results 37 | vec3 ambient = (ambientColor * ambientIntensity) * vDiffuse; 38 | vec3 diffuse = light.color * diff * vDiffuse; 39 | return (ambient + diffuse * light.intensity); 40 | } 41 | `; 42 | 43 | export { lambertEs300, lambertEs100 }; 44 | -------------------------------------------------------------------------------- /src/shaders/chunks/Matcap.glsl.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/hughsk/matcap/blob/master/matcap.glsl 2 | 3 | export default ` 4 | vec2 matcap(vec3 eye, vec3 normal) { 5 | vec3 reflected = reflect(eye, normal); 6 | float m = 2.8284271247461903 * sqrt( reflected.z+1.0 ); 7 | return reflected.xy / m + 0.5; 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /src/shaders/chunks/Math.glsl.ts: -------------------------------------------------------------------------------- 1 | export const definePI = ` 2 | #define PI 3.141592653589793 3 | `; 4 | 5 | export const definePITwo = ` 6 | #define TWO_PI 6.283185307179586 7 | `; 8 | 9 | export const mapLinear = ` 10 | float mapLinear(float value, float in_min, float in_max, float out_min, float out_max) { 11 | return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 12 | } 13 | `; 14 | 15 | export default { 16 | definePI, 17 | definePITwo, 18 | mapLinear 19 | }; 20 | -------------------------------------------------------------------------------- /src/shaders/chunks/Packing.glsl.ts: -------------------------------------------------------------------------------- 1 | export const packNormalToRGB = ` 2 | vec3 packNormalToRGB(const in vec3 normal) { 3 | return normalize(normal) * 0.5 + 0.5; 4 | } 5 | `; 6 | 7 | export default { 8 | packNormalToRGB 9 | }; 10 | -------------------------------------------------------------------------------- /src/shaders/chunks/Phong.glsl.ts: -------------------------------------------------------------------------------- 1 | // https://learnopengl.com/#!Lighting/Multiple-lights 2 | 3 | const phongEs300 = ` 4 | vec3 CalculatePointLight(PointLight light, vec3 normal) { 5 | vec3 lightDirection = normalize(light.position.xyz - vWorldPosition.xyz); 6 | 7 | // diffuse shading 8 | float diff = max(dot(normal, lightDirection), 0.0); 9 | // specular shading 10 | vec3 reflectDirection = reflect(-lightDirection, normal); 11 | 12 | // Fix the spec from showing on the backside by multiplying it by the lambert term 13 | float spec = diff * pow(max(dot(lightDirection, reflectDirection), 0.0), light.shininess.x); 14 | // attenuation 15 | float constant = 1.0; 16 | float linear = 0.09; 17 | float quadratic = 0.032; 18 | 19 | float dist = length(light.position.xyz); 20 | float attenuation = 1.0 / (constant + linear * dist + quadratic * (dist * dist)); 21 | 22 | vec3 ambientColor = vec3(0.5); 23 | float ambientIntensity = 0.5; 24 | 25 | #ifdef ambientLight 26 | ambientColor = uAmbientLight.color.rgb; 27 | ambientIntensity = uAmbientLight.intensity.x; 28 | #endif 29 | 30 | // combine results 31 | vec3 ambient = (ambientColor * ambientIntensity) * vDiffuse; 32 | vec3 diffuse = diff * vDiffuse; 33 | vec3 specular = light.specularColor.rgb * spec * light.shininess.x; 34 | ambient *= attenuation; 35 | diffuse *= attenuation; 36 | specular *= attenuation; 37 | return (ambient + diffuse + specular); 38 | } 39 | `; 40 | 41 | const phongEs100 = ` 42 | vec3 CalculatePointLight(PointLight light, vec3 normal) { 43 | vec3 lightDirection = normalize(light.position - vWorldPosition.xyz); 44 | 45 | // diffuse shading 46 | float diff = max(dot(normal, lightDirection), 0.0); 47 | // specular shading 48 | vec3 reflectDirection = reflect(-lightDirection, normal); 49 | 50 | // Fix the spec from showing on the backside by multiplying it by the lambert term 51 | float spec = diff * pow(max(dot(lightDirection, reflectDirection), 0.0), light.shininess); 52 | // attenuation 53 | float constant = 1.0; 54 | float linear = 0.09; 55 | float quadratic = 0.032; 56 | 57 | float dist = length(light.position); 58 | float attenuation = 1.0 / (constant + linear * dist + quadratic * (dist * dist)); 59 | 60 | vec3 ambientColor = vec3(0.5); 61 | float ambientIntensity = 0.5; 62 | 63 | #ifdef ambientLight 64 | ambientColor = uAmbientLight.color; 65 | ambientIntensity = uAmbientLight.intensity; 66 | #endif 67 | 68 | // combine results 69 | vec3 ambient = (ambientColor * ambientIntensity) * vDiffuse; 70 | vec3 diffuse = diff * vDiffuse; 71 | vec3 specular = light.specularColor * spec * light.shininess; 72 | ambient *= attenuation; 73 | diffuse *= attenuation; 74 | specular *= attenuation; 75 | return (ambient + diffuse + specular); 76 | } 77 | `; 78 | 79 | export { phongEs300, phongEs100 }; 80 | -------------------------------------------------------------------------------- /src/shaders/chunks/PointLights.glsl.ts: -------------------------------------------------------------------------------- 1 | const pointLightsEs300 = ` 2 | struct PointLight { 3 | vec4 position; 4 | vec4 color; 5 | vec4 specularColor; 6 | vec4 shininess; 7 | vec4 intensity; 8 | }; 9 | uniform PointLights { 10 | PointLight uPointLights[#HOOK_POINT_LIGHTS]; 11 | }; 12 | `; 13 | 14 | const pointLightsEs100 = ` 15 | struct PointLight { 16 | vec3 position; 17 | vec3 color; 18 | vec3 specularColor; 19 | float shininess; 20 | float intensity; 21 | }; 22 | uniform PointLight uPointLights[#HOOK_POINT_LIGHTS]; 23 | `; 24 | 25 | export { pointLightsEs300, pointLightsEs100 }; 26 | -------------------------------------------------------------------------------- /src/shaders/chunks/ProjectionView.glsl.ts: -------------------------------------------------------------------------------- 1 | // Es300 2 | export default ` 3 | uniform ProjectionView { 4 | mat4 projectionMatrix; 5 | } uProjectionView; 6 | `; 7 | -------------------------------------------------------------------------------- /src/shaders/chunks/Tonemap.glsl.ts: -------------------------------------------------------------------------------- 1 | // https://raw.githubusercontent.com/stackgl/glsl-gamma/master/out.glsl 2 | 3 | export const tonemapReinhard = ` 4 | vec3 tonemapReinhard(vec3 color) { 5 | return color / (color + vec3(1.0)); 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /src/shaders/chunks/Transpose.glsl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/stackgl/glsl-transpose/blob/master/index.glsl 3 | */ 4 | 5 | export default ` 6 | float transpose(float m) { 7 | return m; 8 | } 9 | 10 | mat2 transpose(mat2 m) { 11 | return mat2(m[0][0], m[1][0], 12 | m[0][1], m[1][1]); 13 | } 14 | 15 | mat3 transpose(mat3 m) { 16 | return mat3(m[0][0], m[1][0], m[2][0], 17 | m[0][1], m[1][1], m[2][1], 18 | m[0][2], m[1][2], m[2][2]); 19 | } 20 | 21 | mat4 transpose(mat4 m) { 22 | return mat4(m[0][0], m[1][0], m[2][0], m[3][0], 23 | m[0][1], m[1][1], m[2][1], m[3][1], 24 | m[0][2], m[1][2], m[2][2], m[3][2], 25 | m[0][3], m[1][3], m[2][3], m[3][3]); 26 | } 27 | `; 28 | -------------------------------------------------------------------------------- /src/shaders/chunks/index.ts: -------------------------------------------------------------------------------- 1 | import * as AmbientLight from './AmbientLight.glsl'; 2 | import Conditionals from './Conditionals.glsl'; 3 | import * as DirectionalLights from './DirectionalLights.glsl'; 4 | import EnvMapCube from './EnvMapCube.glsl'; 5 | import EsVersion from './EsVersion.glsl'; 6 | import Fog from './Fog.glsl'; 7 | import Gamma from './Gamma.glsl'; 8 | import * as Lambert from './Lambert.glsl'; 9 | import Matcap from './Matcap.glsl'; 10 | import Math from './Math.glsl'; 11 | import * as Noise from './Noise.glsl'; 12 | import Packing from './Packing.glsl'; 13 | import * as PointLights from './PointLights.glsl'; 14 | import ProjectionView from './ProjectionView.glsl'; 15 | import * as Tonemap from './Tonemap.glsl'; 16 | import Transpose from './Transpose.glsl'; 17 | 18 | export default { 19 | AmbientLight, 20 | Conditionals, 21 | DirectionalLights, 22 | EnvMapCube, 23 | EsVersion, 24 | Fog, 25 | Gamma, 26 | Lambert, 27 | Matcap, 28 | Math, 29 | Noise, 30 | Packing, 31 | PointLights, 32 | ProjectionView, 33 | Tonemap, 34 | Transpose 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/Array.ts: -------------------------------------------------------------------------------- 1 | import Vector2 from '../math/Vector2'; 2 | import Vector3 from '../math/Vector3'; 3 | export function flatten(arr: any /* [number[] | Vector3[] | Vector2[]] */) { 4 | return arr.reduce((a, b) => { 5 | if (b instanceof Vector2 || b instanceof Vector3) { 6 | return a.concat(...b.v); 7 | } 8 | return a.concat(b); 9 | }, []); 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/Canvas.ts: -------------------------------------------------------------------------------- 1 | export function createCanvas(width = 1, height = 1) { 2 | const canvas = document.createElement('canvas'); 3 | const ctx = canvas.getContext('2d'); 4 | canvas.width = width; 5 | canvas.height = height; 6 | return { 7 | canvas, 8 | ctx 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/Clock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * https://github.com/mrdoob/three.js/blob/master/src/core/Clock.js 4 | */ 5 | 6 | let diff: number; 7 | let newTime: number; 8 | const dateType = window.performance || Date; 9 | 10 | export default class Clock { 11 | public autoStart: boolean; 12 | public startTime: number; 13 | public oldTime: number; 14 | public elapsedTime: number; 15 | public isRunning: boolean; 16 | 17 | constructor(autoStart = false) { 18 | this.startTime = 0; 19 | this.oldTime = 0; 20 | this.elapsedTime = 0; 21 | this.isRunning = autoStart; 22 | } 23 | 24 | public start() { 25 | this.startTime = dateType.now(); 26 | this.oldTime = this.startTime; 27 | this.elapsedTime = 0; 28 | this.isRunning = true; 29 | } 30 | 31 | public stop() { 32 | this.getElapsedTime(); 33 | this.isRunning = false; 34 | } 35 | 36 | public getElapsedTime() { 37 | this.getDelta(); 38 | return this.elapsedTime; 39 | } 40 | 41 | public getDelta() { 42 | diff = 0; 43 | if (this.autoStart && !this.isRunning) { 44 | this.start(); 45 | } 46 | 47 | if (this.isRunning) { 48 | newTime = dateType.now(); 49 | 50 | diff = (newTime - this.oldTime) / 1000; 51 | this.oldTime = newTime; 52 | 53 | this.elapsedTime += diff; 54 | } 55 | 56 | return diff; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/Console.ts: -------------------------------------------------------------------------------- 1 | const enabled = true; 2 | 3 | export const log = (() => { 4 | if (!window.console || !console.log) { 5 | return () => { 6 | return; 7 | }; 8 | } 9 | if (!enabled) 10 | return () => { 11 | return; 12 | }; 13 | return Function.prototype.bind.call(console.log, console); 14 | })(); 15 | 16 | export const clear = (() => { 17 | if (!window.console || !console.clear) { 18 | return () => { 19 | return; 20 | }; 21 | } 22 | if (!enabled) 23 | return () => { 24 | return; 25 | }; 26 | return Function.prototype.bind.call(console.clear, console); 27 | })(); 28 | 29 | export const debug = (() => { 30 | if (!window.console || !console.debug) { 31 | return () => { 32 | return; 33 | }; 34 | } 35 | if (!enabled) 36 | return () => { 37 | return; 38 | }; 39 | return Function.prototype.bind.call(console.debug, console); 40 | })(); 41 | 42 | export const info = (() => { 43 | if (!window.console || !console.info) { 44 | return () => { 45 | return; 46 | }; 47 | } 48 | if (!enabled) 49 | return () => { 50 | return; 51 | }; 52 | return Function.prototype.bind.call(console.info, console); 53 | })(); 54 | 55 | export const warn = (() => { 56 | if (!window.console || !console.warn) { 57 | return () => { 58 | return; 59 | }; 60 | } 61 | if (!enabled) 62 | return () => { 63 | return; 64 | }; 65 | return Function.prototype.bind.call(console.warn, console); 66 | })(); 67 | 68 | export const error = (() => { 69 | if (!window.console || !console.error) { 70 | return () => { 71 | return; 72 | }; 73 | } 74 | if (!enabled) 75 | return () => { 76 | return; 77 | }; 78 | return Function.prototype.bind.call(console.error, console); 79 | })(); 80 | -------------------------------------------------------------------------------- /src/utils/Detect.ts: -------------------------------------------------------------------------------- 1 | export default function() { 2 | try { 3 | const renderingContext = WebGLRenderingContext; 4 | const canvasWebgl = document.createElement('canvas'); 5 | const canvasWebg2 = document.createElement('canvas'); 6 | const webgl2Context = canvasWebg2.getContext('webgl2'); 7 | const webglContext = 8 | canvasWebgl.getContext('webgl') || 9 | canvasWebgl.getContext('experimental-webgl'); 10 | if (renderingContext === undefined) { 11 | return false; 12 | } 13 | return { 14 | webgl: !!webglContext, 15 | webgl2: !!webgl2Context 16 | }; 17 | } catch (error) { 18 | return false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/ObjParser.ts: -------------------------------------------------------------------------------- 1 | import Obj from 'webgl-obj-loader'; 2 | 3 | export default function(data) { 4 | const mesh = new Obj.Mesh(data); 5 | return { 6 | vertices: new Float32Array(mesh.vertices), 7 | normals: new Float32Array(mesh.vertexNormals), 8 | indices: new Uint16Array(mesh.indices), 9 | uvs: new Float32Array(mesh.textures) 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/ShaderParser.ts: -------------------------------------------------------------------------------- 1 | import * as GL from '../core/GL'; 2 | import EsVersion from '../shaders/chunks/EsVersion.glsl'; 3 | 4 | const WORD_REGX = word => new RegExp(`\\b${word}\\b`, 'gi'); 5 | 6 | const LINE_REGX = word => new RegExp(`${word}`, 'gi'); 7 | 8 | const VERTEX = [ 9 | { 10 | match: WORD_REGX('in'), 11 | replace: 'attribute' 12 | }, 13 | { 14 | match: WORD_REGX('out'), 15 | replace: 'varying' 16 | } 17 | ]; 18 | const FRAGMENT = [ 19 | { 20 | match: WORD_REGX('in'), 21 | replace: 'varying' 22 | }, 23 | { 24 | match: LINE_REGX('out vec4 outgoingColor;'), 25 | replace: '' 26 | }, 27 | { 28 | match: WORD_REGX('outgoingColor'), 29 | replace: 'gl_FragColor' 30 | }, 31 | { 32 | match: WORD_REGX('textureProj'), 33 | replace: 'texture2DProj' 34 | }, 35 | { 36 | match: WORD_REGX('texture'), 37 | replace(shader: any): string { 38 | // Find all texture defintions 39 | const textureGlobalRegx = new RegExp('\\btexture\\b', 'gi'); 40 | 41 | // Find single texture definition 42 | const textureSingleRegx = new RegExp('\\btexture\\b', 'i'); 43 | 44 | // Gets the texture call signature e.g texture(uTexture1, vUv); 45 | const textureUniformNameRegx = new RegExp(/texture\(([^)]+)\)/, 'i'); 46 | 47 | // Get all matching occurances 48 | const matches = shader.match(textureGlobalRegx); 49 | let replacement = ''; 50 | 51 | // If nothing matches return 52 | if (matches === null) return shader; 53 | 54 | // Replace each occurance by it's uniform type 55 | for (const i of matches) { 56 | const match = shader.match(textureUniformNameRegx)[0]; 57 | if (match) { 58 | // Extract the uniform name 59 | const uniformName = match.replace('texture(', '').split(',')[0]; 60 | // Find the uniform definition 61 | let uniformType = shader.match(`(.*?) ${uniformName}`, 'i')[1]; 62 | // Remove whitespace at the start 63 | uniformType = uniformType.replace(/^\s+/g, ''); 64 | // Get the sampler type 65 | uniformType = uniformType.split(' ')[1]; 66 | 67 | switch (uniformType) { 68 | case 'sampler2D': { 69 | replacement = 'texture2D'; 70 | break; 71 | } 72 | case 'samplerCube': { 73 | replacement = 'textureCube'; 74 | break; 75 | } 76 | default: 77 | } 78 | } 79 | shader = shader.replace(textureSingleRegx, replacement); 80 | } 81 | return shader; 82 | } 83 | } 84 | ]; 85 | const GENERIC = [ 86 | { 87 | match: LINE_REGX(EsVersion), 88 | replace: '' 89 | } 90 | ]; 91 | 92 | const VERTEX_RULES = [...GENERIC, ...VERTEX]; 93 | 94 | const FRAGMENT_RULES = [...GENERIC, ...FRAGMENT]; 95 | 96 | /** 97 | * Replaces es300 syntax to es100 98 | */ 99 | export default function parse(shader: string, shaderType: string) { 100 | if (GL.webgl2) { 101 | return shader; 102 | } 103 | 104 | const rules = shaderType === 'vertex' ? VERTEX_RULES : FRAGMENT_RULES; 105 | 106 | rules.forEach(rule => { 107 | if (typeof rule.replace === 'function') { 108 | shader = rule.replace(shader); 109 | } else { 110 | shader = shader.replace(rule.match, rule.replace); 111 | } 112 | }); 113 | 114 | // if (shaderType === 'vertex') { 115 | // console.log(shader); 116 | // } 117 | 118 | // if (shaderType === 'fragment') { 119 | // console.log(shader); 120 | // } 121 | 122 | return shader; 123 | } 124 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es6", 5 | "module": "es6", 6 | "moduleResolution": "node", 7 | "lib": [ 8 | "dom", 9 | "es2015", 10 | "es2016" 11 | ] 12 | }, 13 | "compileOnSave": false 14 | } 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "ban": false, 4 | "class-name": true, 5 | "comment-format": [true, "check-space"], 6 | "curly": false, 7 | "forin": true, 8 | "interface-name": [false], 9 | "jsdoc-format": true, 10 | "label-position": true, 11 | "max-classes-per-file": [true, 3], 12 | "member-ordering": [ 13 | true, 14 | "public-before-private", 15 | "static-before-instance", 16 | "variables-before-functions" 17 | ], 18 | "no-any": false, 19 | "no-arg": true, 20 | "no-bitwise": false, 21 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 22 | "no-construct": true, 23 | "no-debugger": true, 24 | "no-shadowed-variable": true, 25 | "no-duplicate-variable": true, 26 | "no-empty": true, 27 | "no-eval": true, 28 | "no-internal-module": true, 29 | "no-require-imports": false, 30 | "no-string-literal": true, 31 | "no-submodule-imports": false, 32 | "no-switch-case-fall-through": true, 33 | "trailing-comma": [false, { "multiline": "never", "singleline": "never" }], 34 | "no-unused-expression": true, 35 | "no-unused-variable": [false], 36 | "no-use-before-declare": false, 37 | "no-var-keyword": true, 38 | "no-var-requires": false, 39 | "object-literal-sort-keys": false, 40 | "prefer-conditional-expression": false, 41 | "radix": true, 42 | "switch-default": true, 43 | "triple-equals": [true, "allow-null-check"], 44 | "typedef": [ 45 | false, 46 | "call-signature", 47 | "parameter", 48 | "property-declaration", 49 | "member-variable-declaration" 50 | ], 51 | "variable-name": [true, "ban-keywords"] 52 | }, 53 | "extends": ["tslint:latest", "tslint-config-prettier"] 54 | } 55 | -------------------------------------------------------------------------------- /webpack.config.build.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const BabiliPlugin = require('babili-webpack-plugin'); 3 | 4 | const production = process.env.NODE_ENV === 'production'; 5 | 6 | module.exports = { 7 | entry: path.join(process.cwd(), 'src/index.ts'), 8 | devtool: 'source-map', 9 | output: { 10 | path: path.join(process.cwd(), 'lib'), 11 | filename: production ? 'ixviii.medium.min.js' : 'ixviii.medium.js', 12 | library: 'ixviii.medium', 13 | libraryTarget: 'umd', 14 | umdNamedDefine: true 15 | }, 16 | resolve: { 17 | extensions: ['.ts', '.js'] 18 | }, 19 | module: { 20 | loaders: [ 21 | { 22 | test: /\.ts$/, 23 | exclude: /node_modules/, 24 | loader: 'babel-loader!ts-loader' 25 | }, 26 | { 27 | test: /\.js$/, 28 | exclude: /node_modules/, 29 | loader: 'babel-loader', 30 | query: { 31 | presets: [ 32 | [ 33 | 'env', 34 | { 35 | targets: { 36 | browsers: [ 37 | 'last 2 versions', 38 | 'ios_saf >= 10.2', 39 | 'not IE <= 10' 40 | ] 41 | } 42 | } 43 | ] 44 | ], 45 | plugins: ['babel-plugin-transform-class-properties'] 46 | } 47 | }, 48 | { 49 | test: /\.json$/, 50 | loader: 'json-loader' 51 | } 52 | ] 53 | }, 54 | stats: 'minimal', 55 | plugins: production ? [new BabiliPlugin()] : [] 56 | }; 57 | -------------------------------------------------------------------------------- /webpack.config.examples.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const BabiliPlugin = require('babili-webpack-plugin'); 4 | 5 | const production = process.env.NODE_ENV === 'production'; 6 | 7 | function isDir(file) { 8 | return fs.statSync(path.join('./examples/src/js/', file)).isDirectory(); 9 | } 10 | const dirs = fs.readdirSync('./examples/src/js/').filter(isDir); 11 | 12 | const entries = {}; 13 | 14 | // To make developing an example faster 15 | // set the env variable EXAMPLE e.g: 16 | // EXAMPLE=linegeometry npm run start 17 | const exampleDir = process.env.EXAMPLE; 18 | 19 | if (exampleDir !== undefined) { 20 | entries[exampleDir] = `./examples/src/js/${exampleDir}/index.js`; 21 | } else { 22 | dirs.forEach(dir => { 23 | entries[dir] = `./examples/src/js/${dir}/index.js`; 24 | }); 25 | } 26 | 27 | module.exports = { 28 | entry: entries, 29 | devtool: 'source-map', 30 | output: { 31 | path: path.join(process.cwd(), 'examples/js'), 32 | filename: '[name].js', 33 | }, 34 | resolve: { 35 | extensions: ['.ts', '.js'], 36 | }, 37 | module: { 38 | loaders: [ 39 | { 40 | test: /\.ts$/, 41 | exclude: /node_modules/, 42 | loader: 'babel-loader!ts-loader', 43 | }, 44 | { 45 | test: /\.js$/, 46 | exclude: /node_modules/, 47 | loader: 'babel-loader', 48 | query: { 49 | presets: [ 50 | [ 51 | 'env', 52 | { 53 | targets: { 54 | browsers: [ 55 | 'last 2 versions', 56 | 'ios_saf >= 10.2', 57 | 'not IE <= 10', 58 | ], 59 | }, 60 | }, 61 | ], 62 | ], 63 | plugins: ['babel-plugin-transform-class-properties'], 64 | }, 65 | }, 66 | ], 67 | }, 68 | stats: 'minimal', 69 | plugins: production ? [new BabiliPlugin()] : [], 70 | }; 71 | --------------------------------------------------------------------------------