├── .gitignore ├── .gitattributes ├── screenshot.png ├── worker.js ├── controls ├── debounce.js └── OrbitControls.svelte ├── scene ├── Layer.svelte ├── Mesh │ ├── shaders │ │ ├── builtin │ │ │ ├── vert.glsl │ │ │ └── frag.glsl │ │ └── default │ │ │ ├── vert.glsl │ │ │ └── frag.glsl │ ├── index.svelte │ ├── Material.mjs │ └── program.mjs ├── lights │ ├── AmbientLight.svelte │ ├── DirectionalLight.svelte │ └── PointLight.svelte ├── Overlay.svelte ├── Target.svelte ├── cameras │ ├── OrthoCamera.svelte │ └── PerspectiveCamera.svelte ├── Group.svelte ├── Point.svelte └── Scene.svelte ├── geometry ├── plane.mjs ├── dodecahedron.mjs ├── sphere.mjs ├── cone.mjs ├── box.mjs ├── Geometry.mjs ├── icosphere.mjs └── polyhedron.mjs ├── package.json ├── LICENSE ├── internal ├── utils.mjs ├── image.mjs ├── index.mjs └── constants.mjs ├── API.md ├── index.mjs ├── scratch └── TAXONOMY.md ├── README.md ├── abstract └── Texture.mjs └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.svelte linguist-language=HTML 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sveltejs/gl/master/screenshot.png -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | self.onmessage = e => { 2 | self.onmessage = null; 3 | eval(e.data); 4 | }; -------------------------------------------------------------------------------- /controls/debounce.js: -------------------------------------------------------------------------------- 1 | export function debounce(fn) { 2 | let scheduled = false; 3 | let event; 4 | 5 | function release() { 6 | fn(event); 7 | scheduled = false; 8 | } 9 | 10 | return function(e) { 11 | if (!scheduled) { 12 | requestAnimationFrame(release); 13 | scheduled = true; 14 | } 15 | 16 | event = e; 17 | }; 18 | } -------------------------------------------------------------------------------- /scene/Layer.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /scene/Mesh/shaders/builtin/vert.glsl: -------------------------------------------------------------------------------- 1 | /* start builtins */ 2 | precision highp float; 3 | 4 | uniform mat4 MODEL; 5 | uniform mat4 PROJECTION; 6 | uniform mat4 VIEW; 7 | uniform mat4 MODEL_INVERSE_TRANSPOSE; 8 | 9 | uniform vec3 CAMERA_WORLD_POSITION; 10 | 11 | struct PointLight { 12 | vec3 location; 13 | vec3 color; 14 | float intensity; 15 | // TODO fall-off etc 16 | }; 17 | 18 | uniform PointLight POINT_LIGHTS[NUM_LIGHTS]; 19 | /* end builtins */ -------------------------------------------------------------------------------- /scene/lights/AmbientLight.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scene/Mesh/shaders/builtin/frag.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_OES_standard_derivatives : enable 2 | 3 | /* start builtins */ 4 | precision highp float; 5 | 6 | struct DirectionalLight { 7 | vec3 direction; 8 | vec3 color; 9 | float intensity; 10 | }; 11 | 12 | struct PointLight { 13 | vec3 location; 14 | vec3 color; 15 | float intensity; 16 | // TODO fall-off etc 17 | }; 18 | 19 | uniform vec3 AMBIENT_LIGHT; 20 | uniform DirectionalLight DIRECTIONAL_LIGHTS[NUM_LIGHTS]; 21 | uniform PointLight POINT_LIGHTS[NUM_LIGHTS]; 22 | /* end builtins */ -------------------------------------------------------------------------------- /scene/Overlay.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /geometry/plane.mjs: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry.mjs'; 2 | import { memoize } from '../internal/utils.mjs'; 3 | 4 | export default memoize(() => { 5 | return new Geometry({ 6 | position: { 7 | data: new Float32Array([ 8 | 1, 1, 0, 9 | -1, 1, 0, 10 | 1, -1, 0, 11 | -1, -1, 0, 12 | ]), 13 | size: 3 14 | }, 15 | 16 | normal: { 17 | data: new Float32Array([ 18 | 0, 0, 1, 19 | 0, 0, 1, 20 | 0, 0, 1, 21 | 0, 0, 1 22 | ]), 23 | size: 3 24 | }, 25 | 26 | uv: { 27 | data: new Float32Array([ 28 | 1, 0, 29 | 0, 0, 30 | 1, 1, 31 | 0, 1 32 | ]), 33 | size: 2 34 | } 35 | }, { 36 | index: new Uint32Array([ 37 | 0, 1, 2, 38 | 3, 2, 1 39 | ]) 40 | }); 41 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sveltejs/gl", 3 | "version": "0.0.37", 4 | "description": "A (very experimental) project to bring WebGL to Svelte", 5 | "main": "index.mjs", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Rich-Harris/gl.git" 12 | }, 13 | "keywords": [ 14 | "Svelte", 15 | "WebGL" 16 | ], 17 | "author": "Rich Harris", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/Rich-Harris/gl/issues" 21 | }, 22 | "homepage": "https://github.com/Rich-Harris/gl#readme", 23 | "devDependencies": { 24 | "tiny-glob": "^0.2.6" 25 | }, 26 | "dependencies": { 27 | "gl-matrix": "^3.0.0", 28 | "yootils": "0.0.16" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /scene/lights/DirectionalLight.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scene/lights/PointLight.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scene/Target.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-19 [these people](https://github.com/sveltejs/gl/graphs/contributors) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /internal/utils.mjs: -------------------------------------------------------------------------------- 1 | export function process_color(color) { 2 | if (typeof color === 'number') { 3 | const r = (color & 0xff0000) >> 16; 4 | const g = (color & 0x00ff00) >> 8; 5 | const b = (color & 0x0000ff); 6 | 7 | return new Float32Array([ 8 | r / 255, 9 | g / 255, 10 | b / 255 11 | ]); 12 | } 13 | 14 | return color; 15 | } 16 | 17 | export function normalize(out, vector = out) { 18 | let total = 0; 19 | for (let i = 0; i < vector.length; i += 1) { 20 | total += vector[i] * vector[i]; 21 | } 22 | 23 | const mag = Math.sqrt(total); 24 | 25 | out[0] = vector[0] / mag; 26 | out[1] = vector[1] / mag; 27 | out[2] = vector[2] / mag; 28 | 29 | return out; 30 | } 31 | 32 | export function create_worker(url, fn) { 33 | const worker = new Worker(url); 34 | const code = fn.toString().replace(/^(function.+?|.+?=>\s*)\{/g, '').slice(0, -1); 35 | 36 | worker.postMessage(code); 37 | 38 | return worker; 39 | } 40 | 41 | export function memoize(fn) { 42 | const cache = new Map(); 43 | return (...args) => { 44 | const hash = JSON.stringify(args); 45 | if (!cache.has(hash)) cache.set(hash, fn(...args)); 46 | return cache.get(hash); 47 | }; 48 | } -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # Using Svelte GL 2 | 3 | You'll need a bundler plugin that recognises .glsl files and turns them into JavaScript strings, e.g. https://github.com/glslify/rollup-plugin-glslify. 4 | 5 | 6 | ## Creating a basic scene 7 | 8 | ```html 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | 37 | 38 | ``` 39 | 40 | Each scene needs: 41 | 42 | * a top-level `` component 43 | * one or more lights — currently we have ``, `` and `` 44 | * one or more `` components — the objects in the scene 45 | 46 | -------------------------------------------------------------------------------- /geometry/dodecahedron.mjs: -------------------------------------------------------------------------------- 1 | import polyhedron from './polyhedron.mjs'; 2 | 3 | // adapted from https://github.com/mrdoob/three.js/blob/master/src/geometries/DodecahedronGeometry.js 4 | // MIT licensed https://github.com/mrdoob/three.js/blob/dev/LICENSE 5 | 6 | const t = ( 1 + Math.sqrt( 5 ) ) / 2; 7 | const r = 1 / t; 8 | 9 | const vertices = [ 10 | // (±1, ±1, ±1) 11 | -1, -1, -1, -1, -1, +1, 12 | -1, +1, -1, -1, +1, +1, 13 | +1, -1, -1, +1, -1, +1, 14 | +1, +1, -1, +1, +1, +1, 15 | 16 | // (0, ±1/φ, ±φ) 17 | 0, -r, -t, 0, -r, +t, 18 | 0, +r, -t, 0, +r, +t, 19 | 20 | // (±1/φ, ±φ, 0) 21 | -r, -t, 0, -r, +t, 0, 22 | +r, -t, 0, +r, +t, 0, 23 | 24 | // (±φ, 0, ±1/φ) 25 | -t, 0, -r, +t, 0, -r, 26 | -t, 0, +r, +t, 0, +r 27 | ]; 28 | 29 | const indices = [ 30 | 3, 11, 7, 3, 7, 15, 3, 15, 13, 31 | 7, 19, 17, 7, 17, 6, 7, 6, 15, 32 | 17, 4, 8, 17, 8, 10, 17, 10, 6, 33 | 8, 0, 16, 8, 16, 2, 8, 2, 10, 34 | 0, 12, 1, 0, 1, 18, 0, 18, 16, 35 | 6, 10, 2, 6, 2, 13, 6, 13, 15, 36 | 2, 16, 18, 2, 18, 3, 2, 3, 13, 37 | 18, 1, 9, 18, 9, 11, 18, 11, 3, 38 | 4, 14, 12, 4, 12, 0, 4, 0, 8, 39 | 11, 9, 5, 11, 5, 19, 11, 19, 7, 40 | 19, 5, 14, 19, 14, 4, 19, 4, 17, 41 | 1, 12, 14, 1, 14, 5, 1, 5, 9 42 | ]; 43 | 44 | export default function({ subdivisions, shading } = {}) { 45 | return polyhedron(vertices, indices, subdivisions, shading); 46 | } -------------------------------------------------------------------------------- /scene/cameras/OrthoCamera.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scene/cameras/PerspectiveCamera.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scene/Mesh/shaders/default/vert.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | attribute vec3 normal; 3 | 4 | varying vec3 v_normal; 5 | 6 | #if defined(has_colormap) || defined(has_specularitymap) || defined(has_normalmap) || defined(has_bumpmap) 7 | #define has_textures true 8 | #endif 9 | 10 | #ifdef has_textures 11 | attribute vec2 uv; 12 | varying vec2 v_uv; 13 | #endif 14 | 15 | #if defined(has_normalmap) || defined(has_bumpmap) 16 | varying vec3 v_view_position; 17 | #endif 18 | 19 | varying vec3 v_surface_to_light[NUM_LIGHTS]; 20 | 21 | #ifdef has_specularity 22 | varying vec3 v_surface_to_view[NUM_LIGHTS]; 23 | #endif 24 | 25 | #ifdef USE_FOG 26 | varying float v_fog_depth; 27 | #endif 28 | 29 | void main() { 30 | vec4 pos = vec4(position, 1.0); 31 | vec4 model_view_pos = VIEW * MODEL * pos; 32 | 33 | v_normal = (MODEL_INVERSE_TRANSPOSE * vec4(normal, 0.0)).xyz; 34 | 35 | #ifdef has_textures 36 | v_uv = uv; 37 | #endif 38 | 39 | #if defined(has_normalmap) || defined(has_bumpmap) 40 | v_view_position = model_view_pos.xyz; 41 | #endif 42 | 43 | #ifdef USE_FOG 44 | v_fog_depth = -model_view_pos.z; 45 | #endif 46 | 47 | for (int i = 0; i < NUM_LIGHTS; i += 1) { 48 | PointLight light = POINT_LIGHTS[i]; 49 | 50 | vec3 surface_world_position = (MODEL * pos).xyz; 51 | v_surface_to_light[i] = light.location - surface_world_position; 52 | 53 | #ifdef has_specularity 54 | v_surface_to_view[i] = CAMERA_WORLD_POSITION - surface_world_position; 55 | #endif 56 | } 57 | 58 | gl_Position = PROJECTION * model_view_pos; 59 | } -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | // scene 2 | export { default as Scene } from './scene/Scene.svelte'; 3 | export { default as Group } from './scene/Group.svelte'; 4 | export { default as Layer } from './scene/Layer.svelte'; 5 | export { default as Mesh } from './scene/Mesh/index.svelte'; 6 | export { default as Overlay } from './scene/Overlay.svelte'; 7 | export { default as Point } from './scene/Point.svelte'; 8 | export { default as Target } from './scene/Target.svelte'; 9 | 10 | // lights 11 | export { default as AmbientLight } from './scene/lights/AmbientLight.svelte'; 12 | export { default as DirectionalLight } from './scene/lights/DirectionalLight.svelte'; 13 | export { default as PointLight } from './scene/lights/PointLight.svelte'; 14 | 15 | // controls 16 | export { default as OrbitControls } from './controls/OrbitControls.svelte'; 17 | 18 | // cameras 19 | export { default as PerspectiveCamera } from './scene/cameras/PerspectiveCamera.svelte'; 20 | export { default as OrthoCamera } from './scene/cameras/OrthoCamera.svelte'; 21 | 22 | // geometry 23 | export { default as Geometry } from './geometry/Geometry.mjs'; 24 | export { default as box } from './geometry/box.mjs'; 25 | export { default as cone } from './geometry/cone.mjs'; 26 | export { default as dodecahedron } from './geometry/dodecahedron.mjs'; 27 | export { default as plane } from './geometry/plane.mjs'; 28 | export { default as icosphere } from './geometry/icosphere.mjs'; 29 | export { default as sphere } from './geometry/sphere.mjs'; 30 | 31 | // textures 32 | export { default as Texture } from './abstract/Texture.mjs'; -------------------------------------------------------------------------------- /scene/Group.svelte: -------------------------------------------------------------------------------- 1 | 46 | 47 | -------------------------------------------------------------------------------- /scene/Point.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | -------------------------------------------------------------------------------- /internal/image.mjs: -------------------------------------------------------------------------------- 1 | import { create_worker } from './utils.mjs'; 2 | 3 | const worker_url = (typeof Blob !== 'undefined' && URL.createObjectURL(new Blob( 4 | [`self.onmessage = e => { self.onmessage = null; eval(e.data); };`], 5 | { type: 'application/javascript' } 6 | ))) || typeof window !== 'undefined' && window.SVELTE_GL_WORKER_URL; 7 | 8 | const image_cache = new Map(); 9 | 10 | export function load_image(src) { 11 | if (!worker_url) { 12 | throw new Error(`Workers cannot be created from Blob URLs in this browser. Please set SVELTE_GL_WORKER_URL`); 13 | } 14 | 15 | if (!image_cache.has(src)) { 16 | image_cache.set(src, new Promise((fulfil, reject) => { 17 | if (typeof createImageBitmap !== 'undefined') { 18 | // TODO pool workers? 19 | const worker = create_worker(worker_url, () => { 20 | self.onmessage = e => { 21 | fetch(e.data, { mode: 'cors' }) 22 | .then(response => response.blob()) 23 | .then(blobData => createImageBitmap(blobData)) 24 | .then(bitmap => { 25 | self.postMessage({ bitmap }, [bitmap]); 26 | }) 27 | .catch(error => { 28 | self.postMessage({ 29 | error: { 30 | message: error.message, 31 | stack: error.stack 32 | } 33 | }); 34 | }); 35 | }; 36 | }); 37 | 38 | worker.onmessage = e => { 39 | if (e.data.error) { 40 | image_cache.delete(src); 41 | reject(e.data.error); 42 | } 43 | 44 | else fulfil(e.data.bitmap); 45 | }; 46 | 47 | worker.postMessage(new URL(src, location.href).href); 48 | } else { 49 | const img = new Image(); 50 | img.crossOrigin = ''; 51 | img.onload = () => fulfil(img); 52 | img.onerror = e => { 53 | image_cache.delete(src); 54 | reject(e); 55 | }; 56 | img.src = src; 57 | } 58 | })); 59 | } 60 | 61 | return image_cache.get(src); 62 | } -------------------------------------------------------------------------------- /internal/index.mjs: -------------------------------------------------------------------------------- 1 | import { getContext, setContext, onDestroy } from 'svelte'; 2 | 3 | export const RENDERER = {}; 4 | export const LAYER = {}; 5 | export const PARENT = {}; 6 | export const CAMERA = {}; 7 | 8 | export function get_scene() { 9 | return getContext(RENDERER); 10 | } 11 | 12 | export function get_layer() { 13 | return getContext(LAYER); 14 | } 15 | 16 | export function get_parent() { 17 | return getContext(PARENT); 18 | } 19 | 20 | export function get_camera() { 21 | return getContext(CAMERA); 22 | } 23 | 24 | export function set_layer(layer) { 25 | setContext(LAYER, layer); 26 | } 27 | 28 | export function set_parent(parent) { 29 | setContext(PARENT, parent); 30 | } 31 | 32 | function remove_index(array, index) { 33 | array[index] = array[array.length - 1]; 34 | array.pop(); 35 | } 36 | 37 | function remove_item(array, item) { 38 | const index = array.indexOf(item); 39 | if (~index) remove_index(array, index); 40 | } 41 | 42 | export function create_layer(index, invalidate) { 43 | let child_index = 0; 44 | 45 | const meshes = []; 46 | const transparent_meshes = []; 47 | const child_layers = []; 48 | 49 | const layer = { 50 | index: 0, 51 | meshes, 52 | transparent_meshes, 53 | child_layers, 54 | needs_sort: false, 55 | needs_transparency_sort: true, 56 | add_mesh: (mesh, existing) => { 57 | if (existing) { 58 | remove_item(mesh.transparent ? meshes : transparent_meshes, mesh); 59 | } 60 | 61 | if (mesh.transparent) { 62 | transparent_meshes.push(mesh); 63 | layer.needs_transparency_sort = true; 64 | } else { 65 | meshes.push(mesh); 66 | } 67 | 68 | onDestroy(() => { 69 | remove_item(meshes, mesh); 70 | remove_item(transparent_meshes, mesh); 71 | invalidate(); 72 | }); 73 | }, 74 | add_child: (index = child_index++) => { 75 | const child_layer = create_layer(index, invalidate); 76 | child_layers.push(child_layer); 77 | 78 | layer.needs_sort = true; 79 | 80 | onDestroy(() => { 81 | remove_item(child_layers, child_layer); 82 | 83 | layer.needs_sort = true; 84 | invalidate(); 85 | }); 86 | 87 | return child_layer; 88 | } 89 | }; 90 | 91 | return layer; 92 | } -------------------------------------------------------------------------------- /geometry/sphere.mjs: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry.mjs'; 2 | import { memoize } from '../internal/utils.mjs'; 3 | 4 | const PI = Math.PI; 5 | const PI2 = PI * 2; 6 | 7 | function create_smooth_geometry(turns, bands) { 8 | const num_vertices = (turns + 1) * (bands + 1); 9 | const num_faces_per_turn = 2 * (bands - 1); 10 | const num_faces = num_faces_per_turn * turns; 11 | 12 | const position = new Float32Array(num_vertices * 3); // doubles as normal 13 | const uv = new Float32Array(num_vertices * 2); 14 | const index = new Uint32Array(num_faces * 3); 15 | 16 | let position_index = 0; 17 | let uv_index = 0; 18 | 19 | for (let i = 0; i <= turns; i += 1) { 20 | const u = i / turns; 21 | 22 | for (let j = 0; j <= bands; j += 1) { 23 | const v = j / bands; 24 | 25 | const x = -Math.cos(u * PI2) * Math.sin(v * PI); 26 | const y = Math.cos(v * PI); 27 | const z = Math.sin(u * PI2) * Math.sin(v * PI); 28 | 29 | position[position_index++] = x; 30 | position[position_index++] = y; 31 | position[position_index++] = z; 32 | 33 | uv[uv_index++] = u; 34 | uv[uv_index++] = v; 35 | } 36 | } 37 | 38 | let face_index = 0; 39 | 40 | for (let i = 0; i < turns; i += 1) { 41 | const offset = i * (bands + 1); 42 | 43 | // north pole face 44 | index[face_index++] = offset + 0; 45 | index[face_index++] = offset + 1; 46 | index[face_index++] = offset + bands + 2; 47 | 48 | for (let j = 1; j < bands - 1; j += 1) { 49 | index[face_index++] = offset + j; 50 | index[face_index++] = offset + j + 1; 51 | index[face_index++] = offset + j + bands + 1; 52 | 53 | index[face_index++] = offset + j + bands + 1; 54 | index[face_index++] = offset + j + 1; 55 | index[face_index++] = offset + j + bands + 2; 56 | } 57 | 58 | index[face_index++] = offset + bands - 1; 59 | index[face_index++] = offset + bands; 60 | index[face_index++] = offset + bands * 2; 61 | } 62 | 63 | return new Geometry({ 64 | position: { 65 | data: position, 66 | size: 3 67 | }, 68 | normal: { 69 | data: position, 70 | size: 3 71 | }, 72 | uv: { 73 | data: uv, 74 | size: 2 75 | } 76 | }, { 77 | index 78 | }); 79 | } 80 | 81 | function create_flat_geometry(turns, bands) { 82 | throw new Error('TODO implement flat geometry'); 83 | } 84 | 85 | export default memoize(({ turns = 8, bands = 6, shading = 'smooth' } = {}) => { 86 | return shading === 'smooth' 87 | ? create_smooth_geometry(turns, bands) 88 | : create_flat_geometry(turns, bands); 89 | }); -------------------------------------------------------------------------------- /scene/Mesh/index.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /geometry/cone.mjs: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry.mjs'; 2 | import { memoize } from '../internal/utils.mjs'; 3 | 4 | function create_flat_geometry(radius, height, sides) { 5 | const num_vertices = sides * 3; 6 | 7 | const position_data = new Float32Array(num_vertices * 3 * 2); 8 | const normal_data = new Float32Array(num_vertices * 3 * 2); 9 | 10 | const ny = radius / height; 11 | 12 | for (let i = 0; i < sides; i += 1) { 13 | const start_angle = (i / sides) * Math.PI * 2; 14 | const end_angle = ((i + 1) / sides) * Math.PI * 2; 15 | const half_angle = (start_angle + end_angle) / 2; 16 | 17 | let o = i * 3 * 3 * 2; 18 | 19 | const x1 = Math.sin(start_angle) * radius; 20 | const z1 = Math.cos(start_angle) * radius; 21 | const x2 = Math.sin(end_angle) * radius; 22 | const z2 = Math.cos(end_angle) * radius; 23 | 24 | // top face 25 | position_data[o + 0] = x1; 26 | position_data[o + 1] = 0; 27 | position_data[o + 2] = z1; 28 | 29 | position_data[o + 3] = x2; 30 | position_data[o + 4] = 0; 31 | position_data[o + 5] = z2; 32 | 33 | position_data[o + 6] = 0; 34 | position_data[o + 7] = height; 35 | position_data[o + 8] = 0; 36 | 37 | const nx = Math.sin(half_angle); 38 | const nz = Math.cos(half_angle); 39 | 40 | const mag = Math.sqrt(nx * nx + ny * ny + nz * nz); 41 | 42 | const nnx = nx / mag; 43 | const nny = ny / mag; 44 | const nnz = nz / mag; 45 | 46 | normal_data[o + 0] = normal_data[o + 3] = normal_data[o + 6] = nnx; 47 | normal_data[o + 1] = normal_data[o + 4] = normal_data[o + 7] = nny; 48 | normal_data[o + 2] = normal_data[o + 5] = normal_data[o + 8] = nnz; 49 | 50 | o += 9; 51 | 52 | // bottom face 53 | position_data[o + 0] = x2; 54 | position_data[o + 1] = 0; 55 | position_data[o + 2] = z2; 56 | 57 | position_data[o + 3] = x1; 58 | position_data[o + 4] = 0; 59 | position_data[o + 5] = z1; 60 | 61 | position_data[o + 6] = 0; 62 | position_data[o + 7] = 0; 63 | position_data[o + 8] = 0; 64 | 65 | normal_data[o + 0] = normal_data[o + 3] = normal_data[o + 6] = 0; 66 | normal_data[o + 1] = normal_data[o + 4] = normal_data[o + 7] = -1; 67 | normal_data[o + 2] = normal_data[o + 5] = normal_data[o + 8] = 0; 68 | } 69 | 70 | return new Geometry({ 71 | position: { 72 | data: position_data, 73 | size: 3 74 | }, 75 | 76 | normal: { 77 | data: normal_data, 78 | size: 3 79 | } 80 | }); 81 | } 82 | 83 | function create_smooth_geometry(radius, height, sides) { 84 | throw new Error('TODO'); 85 | } 86 | 87 | export default memoize(({ radius = 1, height = 1, sides = 12, shading = 'flat' } = {}) => { 88 | return shading === 'flat' 89 | ? create_flat_geometry(radius, height, sides) 90 | : create_smooth_geometry(radius, height, sides); 91 | }); -------------------------------------------------------------------------------- /scene/Mesh/Material.mjs: -------------------------------------------------------------------------------- 1 | import vert_builtin from './shaders/builtin/vert.glsl'; 2 | import frag_builtin from './shaders/builtin/frag.glsl'; 3 | import { compile } from './program.mjs'; 4 | import { process_color } from '../../internal/utils.mjs'; 5 | 6 | function deep_set(obj, path, value) { 7 | const parts = path.replace(/\]$/, '').split(/\[|\]\.|\./); 8 | 9 | while (parts.length > 1) { 10 | const part = parts.shift(); 11 | const next = parts[0]; 12 | 13 | if (!obj[part]) obj[part] = /^\d+$/.test(next) ? [] : {}; 14 | obj = obj[part]; 15 | } 16 | 17 | obj[parts[0]] = value; 18 | } 19 | 20 | export default class Material { 21 | constructor(scene, vert, frag, defines) { 22 | this.scene = scene; 23 | 24 | const gl = scene.gl; 25 | this.gl = gl; 26 | 27 | const { program, uniforms, attributes } = compile( 28 | gl, 29 | scene.defines + defines + '\n\n' + vert_builtin + '\n\n' + vert, 30 | scene.defines + defines + '\n\n' + frag_builtin + '\n\n' + frag 31 | ); 32 | 33 | this.program = program; 34 | this.uniforms = uniforms; 35 | this.attributes = attributes; 36 | 37 | this.uniform_locations = {}; 38 | this.uniforms.forEach(uniform => { 39 | deep_set(this.uniform_locations, uniform.name, gl.getUniformLocation(this.program, uniform.name)); 40 | }); 41 | 42 | this.attribute_locations = {}; 43 | this.attributes.forEach(attribute => { 44 | this.attribute_locations[attribute.name] = gl.getAttribLocation(this.program, attribute.name); 45 | }); 46 | 47 | this.raw_values = {}; 48 | this.values = {}; 49 | } 50 | 51 | set_uniforms(raw_values) { 52 | let texture_index = 0; 53 | 54 | this.uniforms.forEach(({ name, type, loc, setter, processor }) => { 55 | if (name in raw_values) { 56 | let data = raw_values[name]; 57 | 58 | if (data === this.raw_values[name]) return; 59 | 60 | if (type === 35678) { 61 | // texture 62 | this.values[name] = { 63 | texture: data.instantiate(this.scene)._, 64 | index: texture_index++ 65 | }; 66 | 67 | return; 68 | } 69 | 70 | if (typeof data === 'number' && type !== 5126) { 71 | // data provided was a number like 0x123456, 72 | // but it needs to be an array. for now, 73 | // assume it's a color, i.e. vec3 74 | data = process_color(data); 75 | } 76 | 77 | this.values[name] = data; 78 | } 79 | }); 80 | 81 | this.raw_values = raw_values; 82 | } 83 | 84 | apply_uniforms(gl, builtins) { 85 | // TODO if this is the only program, maybe 86 | // we don't need to re-run this each time 87 | this.uniforms.forEach(uniform => { 88 | if (uniform.name in this.values) { 89 | uniform.setter(gl, uniform.loc, this.values[uniform.name]); 90 | } 91 | }); 92 | } 93 | 94 | destroy() { 95 | // TODO 96 | } 97 | } -------------------------------------------------------------------------------- /geometry/box.mjs: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry.mjs'; 2 | import { memoize } from '../internal/utils.mjs'; 3 | 4 | export default memoize(() => { 5 | let pos = 0.5; 6 | let neg = -0.5; 7 | 8 | return new Geometry({ 9 | position: { 10 | data: new Float32Array([ 11 | // front 12 | pos, pos, pos, 13 | neg, pos, pos, 14 | pos, neg, pos, 15 | neg, neg, pos, 16 | 17 | // left 18 | neg, pos, pos, 19 | neg, pos, neg, 20 | neg, neg, pos, 21 | neg, neg, neg, 22 | 23 | // back 24 | neg, pos, neg, 25 | pos, pos, neg, 26 | neg, neg, neg, 27 | pos, neg, neg, 28 | 29 | // right 30 | pos, pos, neg, 31 | pos, pos, pos, 32 | pos, neg, neg, 33 | pos, neg, pos, 34 | 35 | // top 36 | neg, pos, neg, 37 | neg, pos, pos, 38 | pos, pos, neg, 39 | pos, pos, pos, 40 | 41 | // bottom 42 | neg, neg, pos, 43 | neg, neg, neg, 44 | pos, neg, pos, 45 | pos, neg, neg 46 | ]), 47 | size: 3 48 | }, 49 | 50 | normal: { 51 | data: new Float32Array([ 52 | // front 53 | 0, 0, 1, 54 | 0, 0, 1, 55 | 0, 0, 1, 56 | 0, 0, 1, 57 | 58 | // left 59 | -1, 0, 0, 60 | -1, 0, 0, 61 | -1, 0, 0, 62 | -1, 0, 0, 63 | 64 | // back 65 | 0, 0, -1, 66 | 0, 0, -1, 67 | 0, 0, -1, 68 | 0, 0, -1, 69 | 70 | // right 71 | 1, 0, 0, 72 | 1, 0, 0, 73 | 1, 0, 0, 74 | 1, 0, 0, 75 | 76 | // top 77 | 0, 1, 0, 78 | 0, 1, 0, 79 | 0, 1, 0, 80 | 0, 1, 0, 81 | 82 | // bottom 83 | 0, -1, 0, 84 | 0, -1, 0, 85 | 0, -1, 0, 86 | 0, -1, 0 87 | ]), 88 | size: 3 89 | }, 90 | 91 | uv: { 92 | data: new Float32Array([ 93 | // front 94 | 2/4, 1/4, 95 | 1/4, 1/4, 96 | 2/4, 2/4, 97 | 1/4, 2/4, 98 | 99 | // left 100 | 1/4, 1/4, 101 | 0/4, 1/4, 102 | 1/4, 2/4, 103 | 0/4, 2/4, 104 | 105 | // back 106 | 4/4, 1/4, 107 | 3/4, 1/4, 108 | 4/4, 2/4, 109 | 3/4, 2/4, 110 | 111 | // right 112 | 3/4, 1/4, 113 | 2/4, 1/4, 114 | 3/4, 2/4, 115 | 2/4, 2/4, 116 | 117 | // top 118 | 1/4, 0/4, 119 | 1/4, 1/4, 120 | 2/4, 0/4, 121 | 2/4, 1/4, 122 | 123 | // bottom 124 | 1/4, 2/4, 125 | 1/4, 3/4, 126 | 2/4, 2/4, 127 | 2/4, 3/4 128 | ]), 129 | size: 2 130 | } 131 | }, { 132 | index: new Uint32Array([ 133 | // front 134 | 0, 1, 2, 135 | 3, 2, 1, 136 | 137 | // left 138 | 4, 5, 6, 139 | 7, 6, 5, 140 | 141 | // back 142 | 8, 9, 10, 143 | 11, 10, 9, 144 | 145 | // right 146 | 12, 13, 14, 147 | 15, 14, 13, 148 | 149 | // top 150 | 16, 17, 18, 151 | 19, 18, 17, 152 | 153 | // bottom 154 | 20, 21, 22, 155 | 23, 22, 21 156 | ]) 157 | }); 158 | }); -------------------------------------------------------------------------------- /scratch/TAXONOMY.md: -------------------------------------------------------------------------------- 1 | # Taxonomy 2 | 3 | This is a rough attempt to organise the various concepts in Svelte GL. It is subject to change. 4 | 5 | 6 | ## Scene 7 | 8 | The root component. A `` creates a container element and a canvas, and manages all the entities inside, determining when to redraw. 9 | 10 | A scene contains one or more *layers*, and exactly one *camera*. 11 | 12 | > It's possible to imagine that we might want to decouple the scene from the canvas — e.g. we might want to view the same scene from different angles (as in a 3D editor) or render a scene to a texture. 13 | 14 | 15 | ## Layer 16 | 17 | Clears the depth buffer, guaranteeing that objects contained within are drawn on top of whatever is on lower layers. Particularly useful in situations where you'd otherwise experience z-fighting. 18 | 19 | The first layer is 'implicit' — you don't need to create a `` component inside your scene if everything will be on the same layer. 20 | 21 | Layers contain zero or more *objects*. 22 | 23 | 24 | ## Object 25 | 26 | An object is something with a transform (a location and rotation and a scale). 27 | 28 | ### Group 29 | 30 | A logical grouping of objects that can be used to apply transformations to all of them simultaneously. 31 | 32 | ### Camera 33 | 34 | Cameras are objects, whose sole purpose is to generate the view matrix, which manifests as a scene-wide *uniform*. 35 | 36 | ### Light 37 | 38 | Lights also manifest as scene-wide uniforms. 39 | 40 | ### Mesh 41 | 42 | Meshes are objects with a *geometry* and a *material*. They are the only things that actually get drawn, other than overlays. 43 | 44 | 45 | ## Geometry 46 | 47 | A geometry is a collection of *attributes*, a primitive (e.g. triangle), and an (optional) index. 48 | 49 | An attribute is a typed array (normally a Float32Array, I think?) plus a size. 50 | 51 | > There are also `normalized` and `dynamic` properties, which don't currently do anything? 52 | 53 | ## Material 54 | 55 | A material is a combination of a WebGL *program* and a number of uniforms that pertain to that program. Some of those uniforms are 'well known', such as alpha, specularity, colorMap and so on. Some may be specific to the program's shader, if it's not using a default shader. 56 | 57 | Each mesh has its own material. Many materials can share the same program. 58 | 59 | 60 | ## Open questions 61 | 62 | * How can we make it easy to share materials between meshes while also making it easy to define materials in an ad-hoc way as props on meshes (and their wrappers)? 63 | 64 | * Geometries and materials need to belong to a scene (or more accurately, to the `gl` context associated with the scene, though it's useful if they have a reference to the scene so that they can invalidate it). How can we do that in a way that isn't onerous for scene authors? 65 | 66 | * In the medium term, is there a way to design the API such that a compiler could omit built-in shader chunks that weren't used? 67 | 68 | * How can we make shader chunks composable? -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project is deprecated — we recommend https://threlte.xyz/ 2 | 3 | --- 4 | 5 | # @sveltejs/gl 6 | 7 | Declarative WebGL scene graphs inside [Svelte](https://svelte.dev). 8 | 9 | 10 | screenshot 11 | 12 | 13 | ## Here be dragons 14 | 15 | This is not even *close* to production-ready. It's missing many essential features, and isn't optimised. The API is certain to change in substantial ways. You have been warned. 16 | 17 | If you're not fazed by that sort of thing, and would like to help figure out how to make this all work, contributions are very welcome. 18 | 19 | 20 | ## Why are you doing this? 21 | 22 | Someone once described the DOM as 'an API that only a mother could love'. Clearly that person had never tried to build anything with WebGL. Hand-written WebGL code is *phenomenally* ugly and confusing. 23 | 24 | Because of that, people typically reach for libraries like [Three.js](https://threejs.org/) and [regl](http://regl.party/). I use and love both, but they each have characteristics that make them unsuitable for certain tasks: 25 | 26 | * Three is comfortably over half a megabyte minified, making it less than idea for *casual* or *whimsical* graphics — especially on low-powered devices where your JS budget is small. Typically, when you encounter Three in the wild, it's used for a standalone set-piece interactive 27 | * regl is intentionally low-level. I've found it to be difficult to use for quickly iterating on an idea, because you're thinking in terms of *draw calls* rather than a *scene graph* 28 | 29 | Meanwhile, [A-Frame](https://aframe.io/) — which is a web component wrapper around Three — has demonstrated that a declarative markup-driven approach provides a great authoring experience, particularly for certain kinds of scenes. Since it builds on top of Three, it inherits its strengths and weaknesses. 30 | 31 | @sveltejs/gl is several things: 32 | 33 | * An experiment to test a hypothesis that building scene graphs 'the Svelte way' is satisfying and productive, and needn't cost much in terms of JavaScript 34 | * A way to figure out what the right APIs are 35 | * A test to see how much of this can be built in Svelte already (and still perform well) without needing to extend the compiler 36 | * A thing I need at work 37 | 38 | 39 | ## How can I use it? 40 | 41 | Check out [this demo](https://svelte.dev/repl/8d6d139a3d634c2fb1e1ff107c123dd5) to get started. There's not much point writing docs until things are a bit more settled. 42 | 43 | 44 | ## Ideas for future development 45 | 46 | Some of these may be a bit pie-in-the-sky: 47 | 48 | * Post-processing 49 | * Scene graph editor 50 | * WebVR (this would require Svelte to be able to use the VR display's `requestAnimationFrame` instead of the default one, since the refresh rate on VR devices can be 90 or 120fps) 51 | * Interactivity (i.e. raycasting) 52 | * Physics 53 | * Interleaving DOM and GL content (i.e. part of the scene renders behind the DOM, some in front) 54 | * What's the analog of server-side rendering for a GL scene — precomputing high fidelity global illumination maps etc? 55 | 56 | 57 | ## License 58 | 59 | MIT 60 | -------------------------------------------------------------------------------- /abstract/Texture.mjs: -------------------------------------------------------------------------------- 1 | import * as constants from '../internal/constants.mjs'; 2 | import { load_image } from '../internal/image.mjs'; 3 | 4 | const is_power_of_two = n => (n & (n - 1)) === 0; 5 | 6 | const black_pixel = new Uint8Array([0, 0, 0, 255]); 7 | 8 | class TextureInstance { 9 | constructor(scene, texture) { 10 | const { gl } = scene; 11 | 12 | this._ = gl.createTexture(); 13 | 14 | if (typeof texture.data === 'string') { 15 | this.bind(gl, texture, black_pixel); 16 | 17 | texture.ready.then(() => { 18 | this.bind(gl, texture, texture.data); 19 | scene.invalidate(); 20 | }); 21 | } else { 22 | this.bind(gl, texture, texture.data); 23 | } 24 | } 25 | 26 | bind(gl, texture, data) { 27 | gl.bindTexture(constants.TEXTURE_2D, this._); 28 | 29 | if (ArrayBuffer.isView(data)) { 30 | // TODO figure out where this goes 31 | const width = 1; 32 | const height = 1; 33 | 34 | gl.texImage2D(constants.TEXTURE_2D, 0, constants.RGBA, width, height, 0, constants.RGBA, constants.UNSIGNED_BYTE, data); 35 | } else { 36 | gl.texImage2D(constants.TEXTURE_2D, 0, constants.RGBA, constants.RGBA, constants.UNSIGNED_BYTE, data); 37 | } 38 | 39 | const width = 'naturalWidth' in data ? data.naturalWidth : data.width; 40 | const height = 'naturalHeight' in data ? data.naturalHeight : data.height; 41 | 42 | if (is_power_of_two(width) && is_power_of_two(height)) { 43 | gl.generateMipmap(constants.TEXTURE_2D); 44 | 45 | gl.texParameteri(constants.TEXTURE_2D, constants.TEXTURE_WRAP_S, texture.opts.wrapS); 46 | gl.texParameteri(constants.TEXTURE_2D, constants.TEXTURE_WRAP_T, texture.opts.wrapT); 47 | gl.texParameteri(constants.TEXTURE_2D, constants.TEXTURE_MIN_FILTER, texture.opts.minFilter); 48 | } else { 49 | gl.texParameteri(constants.TEXTURE_2D, constants.TEXTURE_WRAP_S, constants.CLAMP_TO_EDGE); 50 | gl.texParameteri(constants.TEXTURE_2D, constants.TEXTURE_WRAP_T, constants.CLAMP_TO_EDGE); 51 | gl.texParameteri(constants.TEXTURE_2D, constants.TEXTURE_MIN_FILTER, constants.LINEAR); 52 | } 53 | } 54 | } 55 | 56 | const caches = new Map(); 57 | let resolved; 58 | 59 | export default class Texture { 60 | constructor(data, opts = {}) { 61 | this.data = data; 62 | 63 | this.opts = { 64 | width: opts.width || 1, 65 | height: opts.height || 1, 66 | internalFormat: opts.format || constants.RGBA, 67 | srcFormat: opts.srcFormat || constants.RGBA, 68 | srcType: opts.srcType || constants.UNSIGNED_BYTE, 69 | wrapS: opts.wrapS || constants.CLAMP_TO_EDGE, 70 | wrapT: opts.wrapT || constants.CLAMP_TO_EDGE, 71 | minFilter: opts.minFilter || constants.LINEAR 72 | }; 73 | 74 | // TODO clamp, mipmaps, etc 75 | 76 | this.hash = JSON.stringify(this.opts); 77 | 78 | this.ready = typeof data === 'string' 79 | ? load_image(data).then(img => { 80 | this.data = img; 81 | }) 82 | : resolved || (resolved = Promise.resolve()); 83 | } 84 | 85 | instantiate(scene, index) { 86 | if (!caches.has(scene)) caches.set(scene, new Map()); 87 | const a = caches.get(scene); 88 | 89 | if (!a.has(this.data)) a.set(this.data, new Map()); 90 | const b = a.get(this.data); 91 | 92 | if (!b.has(this.hash)) b.set(this.hash, new TextureInstance(scene, this, index)); 93 | return b.get(this.hash); 94 | } 95 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @sveltejs/gl changelog 2 | 3 | ## 0.0.37 4 | 5 | * Fix `geometry.update(...)` when length changes 6 | 7 | ## 0.0.36 8 | 9 | * Squelch missing prop warning 10 | 11 | ## 0.0.35 12 | 13 | * Use `translate3d` with overlays, add `will-change: transform` 14 | 15 | ## 0.0.34 16 | 17 | * Add `doubleSided` property to meshes 18 | 19 | ## 0.0.33 20 | 21 | * Use `requestAnimationFrame` to batch renders 22 | 23 | ## 0.0.32 24 | 25 | * Add `geometry.update(key, data)` method 26 | 27 | ## 0.0.31 28 | 29 | * Squelch some prop warnings 30 | * Add `GL.dodecahedron` geometry 31 | 32 | ## 0.0.30 33 | 34 | * Add `snap` prop to `` components, to snap to pixel 35 | 36 | ## 0.0.29 37 | 38 | * Default to sensible blending 39 | 40 | ## 0.0.28 41 | 42 | * Fix `alpha` uniform behaviour in default shader when using `colormap` 43 | * Fix rendering of non-indexed geometry 44 | 45 | ## 0.0.27 46 | 47 | * Add `` component 48 | * Add `emissive` and `emissivemap` uniforms to default material 49 | 50 | ## 0.0.26 51 | 52 | * Fix sorting of transparent meshes ([#16](https://github.com/sveltejs/gl/issues/16)) 53 | 54 | ## 0.0.25 55 | 56 | * Lose context when scene is destroyed 57 | 58 | ## 0.0.24 59 | 60 | * Work around apparent IntersectionObserver bug 61 | 62 | ## 0.0.23 63 | 64 | * Render on entering viewport 65 | 66 | ## 0.0.22 67 | 68 | * Add an orthographic camera 69 | * Pass width and height into scene 70 | * Sort transparent objects 71 | 72 | ## 0.0.21 73 | 74 | * Make image loading scene-independent 75 | 76 | ## 0.0.20 77 | 78 | * Add `fog` property to `` 79 | * `background` property on `` now takes an `[r,g,b]` array or `0xrrggbb` value 80 | * `backgroundOpacity` property controls background opacity 81 | 82 | ## 0.0.19 83 | 84 | * Pass through `depthTest` and `blend` 85 | * Allow more than one texture 86 | 87 | ## 0.0.18 88 | 89 | * Overhaul ([#22](https://github.com/sveltejs/gl/pull/22)) 90 | 91 | ## 0.0.17 92 | 93 | * Default to crossOrigin images 94 | 95 | ## 0.0.16 96 | 97 | * Allow meshes to receive frag, vert, blend properties 98 | 99 | ## 0.0.15 100 | 101 | * Declare extensions first, due to change in Chrome 75 102 | 103 | ## 0.0.14 104 | 105 | * Allow `pixelRatio` to be specified 106 | 107 | ## 0.0.13 108 | 109 | * Fetch image data in `cors` mode 110 | 111 | ## 0.0.12 112 | 113 | * Only update when canvas is visible 114 | 115 | ## 0.0.10-11 116 | 117 | * Fix resize glitches 118 | 119 | ## 0.0.9 120 | 121 | * Support primitives other than lines 122 | 123 | ## 0.0.8 124 | 125 | * Fix blending formula 126 | 127 | ## 0.0.7 128 | 129 | * Default to `highp` floats 130 | * Work around some shader bug I don't understand 131 | * Depth test by default 132 | 133 | ## 0.0.6 134 | 135 | * Fix serialization of minified code sent to workers 136 | 137 | ## 0.0.5 138 | 139 | * Add `` component 140 | * Add `user-select: none` on overlays 141 | * Load and decode images off the main thread 142 | * Allow depth testing to be disabled per-material 143 | 144 | ## 0.0.4 145 | 146 | * Bump mapping 147 | * New `Sphere` implementation 148 | 149 | ## 0.0.3 150 | 151 | * Don't reinitialise element buffers unnecessarily 152 | * Enable `minDistance` and `maxDistance` on OrbitControls 153 | 154 | ## 0.0.2 155 | 156 | * Throttle OrbitControls events 157 | * Add touch support to OrbitControls 158 | 159 | ## 0.0.1 160 | 161 | * First experimental release -------------------------------------------------------------------------------- /geometry/Geometry.mjs: -------------------------------------------------------------------------------- 1 | class GeometryInstance { 2 | constructor(scene, program, attributes, index, primitive, count) { 3 | this.scene = scene; 4 | const gl = scene.gl; 5 | 6 | this.attributes = attributes; 7 | this.index = index; 8 | this.primitive = primitive; 9 | this.count = count; 10 | 11 | this.locations = {}; 12 | this.buffers = {}; 13 | 14 | for (const key in attributes) { 15 | const attribute = attributes[key]; 16 | 17 | this.locations[key] = gl.getAttribLocation(program, key); 18 | 19 | const buffer = gl.createBuffer(); 20 | this.buffers[key] = buffer; 21 | 22 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 23 | gl.bufferData(gl.ARRAY_BUFFER, attribute.data, attribute.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); 24 | } 25 | 26 | if (index) { 27 | const buffer = gl.createBuffer(); 28 | this.buffers.__index = buffer; 29 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); 30 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index, gl.STATIC_DRAW); 31 | } 32 | } 33 | 34 | set_attributes(gl) { 35 | for (const key in this.attributes) { 36 | const attribute = this.attributes[key]; 37 | 38 | const loc = this.locations[key]; 39 | if (loc < 0) continue; // attribute is unused by current program 40 | 41 | const { 42 | size = 3, 43 | type = gl.FLOAT, 44 | normalized = false, 45 | stride = 0, 46 | offset = 0 47 | } = attribute; 48 | 49 | // Bind the position buffer. 50 | const buffer = this.buffers[key]; 51 | 52 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 53 | 54 | // Turn on the attribute 55 | gl.enableVertexAttribArray(loc); 56 | 57 | gl.vertexAttribPointer( 58 | loc, 59 | size, 60 | type, 61 | normalized, 62 | stride, 63 | offset 64 | ); 65 | } 66 | } 67 | 68 | update(k, data, count) { 69 | const scene = this.scene; 70 | const { gl } = scene; 71 | 72 | const attribute = this.attributes[k]; 73 | const buffer = this.buffers[k]; 74 | 75 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 76 | gl.bufferData(gl.ARRAY_BUFFER, attribute.data = data, attribute.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); 77 | 78 | this.count = count; 79 | 80 | if (count === Infinity) { 81 | throw new Error(`GL.Geometry must be instantiated with one or more { data, size } attributes`); 82 | } 83 | 84 | scene.invalidate(); 85 | } 86 | } 87 | 88 | export default class Geometry { 89 | constructor(attributes = {}, opts = {}) { 90 | this.attributes = attributes; 91 | 92 | const { index, primitive = 'TRIANGLES' } = opts; 93 | this.index = index; 94 | this.primitive = primitive.toUpperCase(); 95 | this.count = get_count(attributes); 96 | 97 | this.instances = new Map(); 98 | } 99 | 100 | instantiate(scene, program) { 101 | if (!this.instances.has(program)) { 102 | this.instances.set(program, new GeometryInstance( 103 | scene, 104 | program, 105 | this.attributes, 106 | this.index, 107 | this.primitive, 108 | this.count 109 | )); 110 | } 111 | 112 | return this.instances.get(program); 113 | } 114 | 115 | update(k, data) { 116 | this.attributes[k].data = data; 117 | this.count = get_count(this.attributes); 118 | 119 | this.instances.forEach(instance => { 120 | instance.update(k, data, this.count); 121 | }); 122 | } 123 | } 124 | 125 | function get_count(attributes) { 126 | let min = Infinity; 127 | 128 | for (const k in attributes) { 129 | const count = attributes[k].data.length / attributes[k].size; 130 | if (count < min) min = count; 131 | } 132 | 133 | return min; 134 | } -------------------------------------------------------------------------------- /geometry/icosphere.mjs: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry.mjs'; 2 | import { memoize, normalize } from '../internal/utils.mjs'; 3 | 4 | const p = 0.85065080835204; 5 | const q = 0.5257311121191336; 6 | 7 | const position = new Float32Array([ 8 | -q, +p, 0, 9 | +q, +p, 0, 10 | -q, -p, 0, 11 | +q, -p, 0, 12 | 0, -q, +p, 13 | 0, +q, +p, 14 | 0, -q, -p, 15 | 0, +q, -p, 16 | +p, 0, -q, 17 | +p, 0, +q, 18 | -p, 0, -q, 19 | -p, 0, +q 20 | ]); 21 | 22 | const index = new Uint16Array([ 23 | 0, 11, 5, 24 | 0, 5, 1, 25 | 0, 1, 7, 26 | 0, 7, 10, 27 | 0, 10, 11, 28 | 1, 5, 9, 29 | 5, 11, 4, 30 | 11, 10, 2, 31 | 10, 7, 6, 32 | 7, 1, 8, 33 | 3, 9, 4, 34 | 3, 4, 2, 35 | 3, 2, 6, 36 | 3, 6, 8, 37 | 3, 8, 9, 38 | 4, 9, 5, 39 | 2, 4, 11, 40 | 6, 2, 10, 41 | 8, 6, 7, 42 | 9, 8, 1 43 | ]); 44 | 45 | const smooth_geometry = [ 46 | new Geometry({ 47 | position: { data: position, size: 3 }, 48 | normal: { data: position, size: 3 } 49 | }, { index }) 50 | ]; 51 | 52 | function subdivide(geometry) { 53 | const index = new Uint32Array(geometry.index.length * 4); 54 | 55 | const old_position = geometry.attributes.position.data; 56 | const new_positions = []; 57 | const lookup = new Map(); 58 | 59 | function get_index(point) { 60 | const hash = `${point[0].toPrecision(6)},${point[1].toPrecision(6)},${point[2].toPrecision(6)}`; 61 | 62 | if (lookup.has(hash)) { 63 | return lookup.get(hash); 64 | } 65 | 66 | const index = new_positions.length; 67 | lookup.set(hash, index); 68 | new_positions[index] = point; 69 | return index; 70 | } 71 | 72 | function mid(a, b) { 73 | return get_index([ 74 | (a[0] + b[0]) / 2, 75 | (a[1] + b[1]) / 2, 76 | (a[2] + b[2]) / 2 77 | ]); 78 | } 79 | 80 | for (let i = 0; i < geometry.index.length; i += 3) { 81 | const c0 = geometry.index[i + 0]; 82 | const c1 = geometry.index[i + 1]; 83 | const c2 = geometry.index[i + 2]; 84 | 85 | const v0 = [ 86 | old_position[c0 * 3 + 0], 87 | old_position[c0 * 3 + 1], 88 | old_position[c0 * 3 + 2] 89 | ]; 90 | 91 | const v1 = [ 92 | old_position[c1 * 3 + 0], 93 | old_position[c1 * 3 + 1], 94 | old_position[c1 * 3 + 2] 95 | ]; 96 | 97 | const v2 = [ 98 | old_position[c2 * 3 + 0], 99 | old_position[c2 * 3 + 1], 100 | old_position[c2 * 3 + 2] 101 | ]; 102 | 103 | const a = mid(v0, v1); 104 | const b = mid(v1, v2); 105 | const c = mid(v2, v0); 106 | 107 | // four new faces 108 | const j = i * 4; 109 | 110 | index[j + 0] = get_index(v0); 111 | index[j + 1] = a; 112 | index[j + 2] = c; 113 | 114 | index[j + 3] = get_index(v1); 115 | index[j + 4] = b; 116 | index[j + 5] = a; 117 | 118 | index[j + 6] = get_index(v2); 119 | index[j + 7] = c; 120 | index[j + 8] = b; 121 | 122 | index[j + 9] = a 123 | index[j + 10] = b; 124 | index[j + 11] = c; 125 | } 126 | 127 | const position = new Float32Array(new_positions.length * 3); 128 | for (let i = 0; i < new_positions.length; i += 1) { 129 | const vector = normalize(new_positions[i]); 130 | 131 | position[i * 3 + 0] = vector[0]; 132 | position[i * 3 + 1] = vector[1]; 133 | position[i * 3 + 2] = vector[2]; 134 | } 135 | 136 | return new Geometry({ 137 | position: { data: position, size: 3 }, 138 | normal: { data: position, size: 3 } 139 | }, { index }) 140 | } 141 | 142 | function create_smooth_geometry(subdivisions = 0) { 143 | if (!smooth_geometry[subdivisions]) { 144 | const geometry = create_smooth_geometry(subdivisions - 1); 145 | smooth_geometry[subdivisions] = subdivide(geometry); 146 | } 147 | 148 | return smooth_geometry[subdivisions]; 149 | } 150 | 151 | function create_flat_geometry(subdivisions) { 152 | throw new Error(`TODO implement flat sphere geometry`); 153 | } 154 | 155 | export default memoize(({ subdivisions = 0, shading = 'smooth' } = {}) => { 156 | return shading === 'smooth' 157 | ? create_smooth_geometry(subdivisions) 158 | : create_flat_geometry(subdivisions); 159 | }); -------------------------------------------------------------------------------- /scene/Mesh/program.mjs: -------------------------------------------------------------------------------- 1 | const caches = new Map(); 2 | 3 | const setters = { 4 | [5126]: (gl, loc, data) => gl.uniform1f(loc, data), 5 | [35664]: (gl, loc, data) => gl.uniform2fv(loc, data), 6 | [35665]: (gl, loc, data) => gl.uniform3fv(loc, data), 7 | [35666]: (gl, loc, data) => gl.uniform4fv(loc, data), 8 | 9 | [35674]: (gl, loc, data) => gl.uniformMatrix2fv(loc, false, data), 10 | [35675]: (gl, loc, data) => gl.uniformMatrix3fv(loc, false, data), 11 | [35676]: (gl, loc, data) => gl.uniformMatrix4fv(loc, false, data), 12 | 13 | [35678]: (gl, loc, data) => { 14 | gl.activeTexture(gl[`TEXTURE${data.index}`]); 15 | gl.bindTexture(gl.TEXTURE_2D, data.texture); 16 | gl.uniform1i(loc, data.index); 17 | } 18 | }; 19 | 20 | export function compile(gl, vert, frag) { 21 | if (!caches.has(gl)) caches.set(gl, new Map()); 22 | const cache = caches.get(gl); 23 | 24 | const hash = vert + frag; 25 | if (!cache.has(hash)) { 26 | const program = create_program(gl, vert, frag); 27 | const uniforms = get_uniforms(gl, program); 28 | const attributes = get_attributes(gl, program); 29 | 30 | cache.set(hash, { program, uniforms, attributes }); 31 | } 32 | 33 | return cache.get(hash); 34 | } 35 | 36 | export function remove_program(info) { 37 | const cache = caches.get(info.gl); 38 | 39 | if (--info.users === 0) { 40 | info.gl.deleteProgram(info.program); 41 | cache.delete(info.hash); 42 | } 43 | } 44 | 45 | function pad(num, len = 4) { 46 | num = String(num); 47 | while (num.length < len) num = ` ${num}`; 48 | return num; 49 | } 50 | 51 | function repeat(str, i) { 52 | let result = ''; 53 | while (i--) result += str; 54 | return result; 55 | } 56 | 57 | function create_shader(gl, type, source, label) { 58 | const shader = gl.createShader(type); 59 | gl.shaderSource(shader, source); 60 | gl.compileShader(shader); 61 | 62 | if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 63 | return shader; 64 | } 65 | 66 | const log = gl.getShaderInfoLog(shader); 67 | const match = /ERROR: (\d+):(\d+): (.+)/.exec(log); 68 | 69 | if (match) { 70 | const c = +match[1]; 71 | const l = +match[2] - 1; 72 | 73 | console.log('%c' + match[3], 'font-weight: bold; color: red'); 74 | 75 | const lines = source.split('\n'); 76 | for (let i = 0; i < lines.length; i += 1) { 77 | if (Math.abs(l - i) > 5) continue; 78 | 79 | const line = lines[i].replace(/^\t+/gm, tabs => repeat(' ', tabs.length * 4)); 80 | const indent = /^\s+/.exec(line); 81 | 82 | const str = `${pad(i)}: ${line}`; 83 | 84 | if (i === l) { 85 | console.log('%c' + str, 'font-weight: bold; color: red'); 86 | console.log('%c' + (indent && indent[0] || '') + repeat(' ', c + 6) + '^', 'color: red'); 87 | } else { 88 | console.log(str); 89 | } 90 | } 91 | 92 | throw new Error(`Failed to compile ${label} shader`); 93 | } 94 | 95 | throw new Error(`Failed to compile ${label} shader:\n${log}`); 96 | } 97 | 98 | function create_program(gl, vert, frag) { 99 | const program = gl.createProgram(); 100 | 101 | gl.attachShader(program, create_shader(gl, gl.VERTEX_SHADER, vert, 'vertex')); 102 | gl.attachShader(program, create_shader(gl, gl.FRAGMENT_SHADER, frag, 'fragment')); 103 | gl.linkProgram(program); 104 | 105 | const success = gl.getProgramParameter(program, gl.LINK_STATUS); 106 | if (!success) { 107 | console.log(gl.getProgramInfoLog(program)); 108 | throw new Error(`Failed to compile program:\n${gl.getProgramInfoLog(program)}`); 109 | } 110 | 111 | return program; 112 | } 113 | 114 | function get_uniforms(gl, program) { 115 | const uniforms = []; 116 | 117 | const n = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); 118 | 119 | for (let i = 0; i < n; i += 1) { 120 | let { size, type, name } = gl.getActiveUniform(program, i); 121 | const loc = gl.getUniformLocation(program, name); 122 | const setter = setters[type]; 123 | 124 | if (!setter) { 125 | throw new Error(`not implemented ${type} (${name})`); 126 | } 127 | 128 | uniforms.push({ size, type, name, setter, loc }); 129 | } 130 | 131 | return uniforms; 132 | } 133 | 134 | function get_attributes(gl, program) { 135 | const attributes = []; 136 | 137 | const n = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); 138 | 139 | for (let i = 0; i < n; i += 1) { 140 | let { size, type, name } = gl.getActiveAttrib(program, i); 141 | name = name.replace('[0]', ''); 142 | const loc = gl.getAttribLocation(program, name); 143 | 144 | attributes.push({ size, type, name, loc }); 145 | } 146 | 147 | return attributes; 148 | } -------------------------------------------------------------------------------- /scene/Mesh/shaders/default/frag.glsl: -------------------------------------------------------------------------------- 1 | // mesh uniforms 2 | #if defined(has_colormap) || defined(has_specularitymap) || defined(has_normalmap) || defined(has_bumpmap) || defined(has_emissivemap) 3 | #define has_textures true 4 | #endif 5 | 6 | #ifdef has_textures 7 | varying vec2 v_uv; 8 | #endif 9 | 10 | #ifdef has_specularity 11 | uniform float specularity; 12 | #endif 13 | 14 | #ifdef has_colormap 15 | uniform sampler2D colormap; 16 | #endif 17 | 18 | #ifdef has_emissivemap 19 | uniform sampler2D emissivemap; 20 | #endif 21 | 22 | #ifdef has_specularitymap 23 | uniform sampler2D specularitymap; 24 | #endif 25 | 26 | #ifdef has_bumpmap 27 | uniform sampler2D bumpmap; 28 | 29 | // adapted from https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/bumpmap_pars_fragment.glsl.js 30 | // https://github.com/mrdoob/three.js/blob/dev/LICENSE 31 | vec2 dHdxy_fwd() { 32 | vec2 dSTdx = dFdx(v_uv); 33 | vec2 dSTdy = dFdy(v_uv); 34 | 35 | float Hll = texture2D(bumpmap, v_uv).x; 36 | float dBx = texture2D(bumpmap, v_uv + dSTdx).x - Hll; 37 | float dBy = texture2D(bumpmap, v_uv + dSTdy).x - Hll; 38 | 39 | #ifdef has_bumpscale 40 | Hll *= bumpscale; 41 | dBx *= bumpscale; 42 | dBy *= bumpscale; 43 | #endif 44 | 45 | return vec2(dBx, dBy); 46 | } 47 | 48 | vec3 perturbNormalArb(vec3 surf_pos, vec3 surface_normal, vec2 dHdxy) { 49 | // Workaround for Adreno 3XX dFd*(vec3) bug. See #9988 50 | vec3 vSigmaX = vec3(dFdx(surf_pos.x), dFdx(surf_pos.y), dFdx(surf_pos.z)); 51 | vec3 vSigmaY = vec3(dFdy(surf_pos.x), dFdy(surf_pos.y), dFdy(surf_pos.z)); 52 | vec3 vN = surface_normal; 53 | 54 | vec3 R1 = cross(vSigmaY, vN); 55 | vec3 R2 = cross(vN, vSigmaX); 56 | 57 | float fDet = dot(vSigmaX, R1); 58 | 59 | fDet *= (float(gl_FrontFacing) * 2.0 - 1.0); 60 | 61 | vec3 vGrad = sign(fDet) * (dHdxy.x * R1 + dHdxy.y * R2); 62 | return normalize(abs(fDet) * surface_normal - vGrad); 63 | } 64 | #endif 65 | 66 | #ifdef has_bumpscale 67 | uniform float bumpscale; 68 | #endif 69 | 70 | #ifdef has_normalmap 71 | uniform sampler2D normalmap; 72 | 73 | vec3 perturbNormal2Arb(vec3 eye_pos, vec3 surface_normal) { 74 | // Workaround for Adreno 3XX dFd*(vec3) bug. See https://github.com/mrdoob/three.js/issues/9988 75 | vec3 q0 = vec3(dFdx(eye_pos.x), dFdx(eye_pos.y), dFdx(eye_pos.z)); 76 | vec3 q1 = vec3(dFdy(eye_pos.x), dFdy(eye_pos.y), dFdy(eye_pos.z)); 77 | 78 | vec2 st0 = dFdx(v_uv.st); 79 | vec2 st1 = dFdy(v_uv.st); 80 | 81 | // TODO derivative functions don't seem to work on some 82 | // mobile phones - need to investigate 83 | if (length(q0) == 0.0) { 84 | return surface_normal; 85 | } 86 | 87 | float scale = sign(st1.t * st0.s - st0.t * st1.s); // we do not care about the magnitude 88 | 89 | vec3 S = normalize((q0 * st1.t - q1 * st0.t) * scale); 90 | vec3 T = normalize((-q0 * st1.s + q1 * st0.s) * scale); 91 | vec3 N = normalize(surface_normal); 92 | mat3 tsn = mat3(S, T, N); 93 | vec3 mapN = texture2D(normalmap, v_uv).xyz * 2.0 - 1.0; 94 | 95 | // TODO 96 | // mapN.xy *= NORMAL_SCALE; 97 | 98 | mapN.xy *= (float(gl_FrontFacing) * 2.0 - 1.0); 99 | return normalize(tsn * mapN); 100 | } 101 | #endif 102 | 103 | #ifdef has_color 104 | uniform vec3 color; 105 | #endif 106 | 107 | #ifdef has_emissive 108 | uniform vec3 emissive; 109 | #endif 110 | 111 | #ifdef has_alpha 112 | uniform float alpha; 113 | #endif 114 | 115 | #ifdef USE_FOG 116 | uniform vec3 FOG_COLOR; 117 | uniform float FOG_DENSITY; 118 | varying float v_fog_depth; 119 | #endif 120 | 121 | varying vec3 v_normal; 122 | 123 | #if defined(has_normalmap) || defined(has_bumpmap) 124 | varying vec3 v_view_position; 125 | #endif 126 | 127 | varying vec3 v_surface_to_light[NUM_LIGHTS]; 128 | varying vec3 v_surface_to_view[NUM_LIGHTS]; 129 | 130 | void main () { 131 | vec3 normal = normalize(v_normal); 132 | 133 | #ifdef has_bumpmap 134 | normal = perturbNormalArb(-v_view_position, normal, dHdxy_fwd()); 135 | #elif defined(has_normalmap) 136 | normal = perturbNormal2Arb(-v_view_position, normal); 137 | #endif 138 | 139 | vec3 lighting = vec3(0.0); 140 | vec3 spec_amount = vec3(0.0); 141 | 142 | // directional lights 143 | for (int i = 0; i < NUM_LIGHTS; i += 1) { 144 | DirectionalLight light = DIRECTIONAL_LIGHTS[i]; 145 | 146 | float multiplier = clamp(dot(normal, -light.direction), 0.0, 1.0); 147 | lighting += multiplier * light.color * light.intensity; 148 | } 149 | 150 | // point lights 151 | for (int i = 0; i < NUM_LIGHTS; i += 1) { 152 | PointLight light = POINT_LIGHTS[i]; 153 | 154 | vec3 surface_to_light = normalize(v_surface_to_light[i]); 155 | 156 | float multiplier = clamp(dot(normal, surface_to_light), 0.0, 1.0); // TODO is clamp necessary? 157 | lighting += multiplier * light.color * light.intensity; 158 | 159 | #ifdef has_specularity 160 | vec3 surface_to_view = normalize(v_surface_to_view[i]); 161 | vec3 half_vector = normalize(surface_to_light + surface_to_view); 162 | float spec = clamp(dot(normal, half_vector), 0.0, 1.0); 163 | 164 | #ifdef has_specularitymap 165 | spec *= texture2D(specularitymap, v_uv).r; 166 | #endif 167 | 168 | spec_amount += specularity * spec * light.color * light.intensity; 169 | #endif 170 | } 171 | 172 | #if defined(has_colormap) 173 | gl_FragColor = texture2D(colormap, v_uv); 174 | #elif defined(has_color) 175 | gl_FragColor = vec4(color, 1.0); 176 | #endif 177 | 178 | #ifdef has_alpha 179 | gl_FragColor.a *= alpha; 180 | #endif 181 | 182 | gl_FragColor.rgb *= mix(AMBIENT_LIGHT, vec3(1.0, 1.0, 1.0), lighting); 183 | gl_FragColor.rgb += spec_amount; 184 | 185 | #if defined(has_emissivemap) 186 | gl_FragColor.rgb += texture2D(emissivemap, v_uv); 187 | #elif defined(has_emissive) 188 | gl_FragColor.rgb += emissive; 189 | #endif 190 | 191 | #ifdef USE_FOG 192 | gl_FragColor.rgb = mix( 193 | gl_FragColor.rgb, 194 | FOG_COLOR, 195 | 1.0 - exp(-FOG_DENSITY * FOG_DENSITY * v_fog_depth * v_fog_depth) 196 | ); 197 | #endif 198 | } -------------------------------------------------------------------------------- /geometry/polyhedron.mjs: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry.mjs'; 2 | import { memoize, normalize } from '../internal/utils.mjs'; 3 | 4 | // adapted from https://github.com/mrdoob/three.js/blob/master/src/geometries/PolyhedronGeometry.js 5 | // MIT licensed https://github.com/mrdoob/three.js/blob/dev/LICENSE 6 | 7 | function lerp(a, b, t) { 8 | return a.map((aa, i) => { 9 | const bb = b[i]; 10 | return aa + (bb - aa) * t; 11 | }); 12 | } 13 | 14 | function set2(vector, a, b) { 15 | vector[0] = a; 16 | vector[1] = b; 17 | } 18 | 19 | function set3(vector, a, b, c) { 20 | vector[0] = a; 21 | vector[1] = b; 22 | vector[2] = c; 23 | } 24 | 25 | function correct_uvs(vertex_buffer, uv_buffer) { 26 | const a = new Float32Array(3); 27 | const b = new Float32Array(3); 28 | const c = new Float32Array(3); 29 | 30 | const centroid = new Float32Array(3); 31 | 32 | const uv_a = new Float32Array(2); 33 | const uv_b = new Float32Array(2); 34 | const uv_c = new Float32Array(2); 35 | 36 | for (let i = 0, j = 0; i < vertex_buffer.length; i += 9, j += 6) { 37 | set3(a, vertex_buffer[i + 0], vertex_buffer[i + 1], vertex_buffer[i + 2]); 38 | set3(b, vertex_buffer[i + 3], vertex_buffer[i + 4], vertex_buffer[i + 5]); 39 | set3(c, vertex_buffer[i + 6], vertex_buffer[i + 7], vertex_buffer[i + 8]); 40 | 41 | set2(uv_a, uv_buffer[j + 0], uv_buffer[j + 1]); 42 | set2(uv_b, uv_buffer[j + 2], uv_buffer[j + 3]); 43 | set2(uv_c, uv_buffer[j + 4], uv_buffer[j + 5]); 44 | 45 | centroid[0] = (a[0] + b[0] + c[0]) / 3; 46 | centroid[1] = (a[1] + b[1] + c[1]) / 3; 47 | centroid[2] = (a[2] + b[2] + c[2]) / 3; 48 | 49 | const azi = azimuth(centroid); 50 | 51 | correct_uv(uv_buffer, uv_a, j + 0, a, azi); 52 | correct_uv(uv_buffer, uv_b, j + 2, b, azi); 53 | correct_uv(uv_buffer, uv_c, j + 4, c, azi); 54 | } 55 | } 56 | 57 | function correct_uv(uv_buffer, uv, stride, vector, azimuth) { 58 | if ((azimuth < 0) && (uv[0] === 1)) { 59 | uv_buffer[stride] = uv[0] - 1; 60 | } 61 | 62 | if ((vector[0] === 0) && (vector[2] === 0)) { 63 | uv_buffer[stride] = azimuth / 2 / Math.PI + 0.5; 64 | } 65 | } 66 | 67 | function correct_seam(uv_buffer) { 68 | // handle case when face straddles the seam 69 | for (var i = 0; i < uv_buffer.length; i += 6) { 70 | // uv data of a single face 71 | var x0 = uv_buffer[i + 0]; 72 | var x1 = uv_buffer[i + 2]; 73 | var x2 = uv_buffer[i + 4]; 74 | 75 | var max = Math.max(x0, x1, x2); 76 | var min = Math.min(x0, x1, x2); 77 | 78 | // 0.9 is somewhat arbitrary 79 | if (max > 0.9 && min < 0.1) { 80 | if (x0 < 0.2) uv_buffer[i + 0] += 1; 81 | if (x1 < 0.2) uv_buffer[i + 2] += 1; 82 | if (x2 < 0.2) uv_buffer[i + 4] += 1; 83 | } 84 | } 85 | } 86 | 87 | // Angle around the Y axis, counter-clockwise when looking from above. 88 | function azimuth(vector) { 89 | return Math.atan2(vector[2], - vector[0]); 90 | } 91 | 92 | // Angle above the XZ plane. 93 | function inclination(vector) { 94 | return Math.atan2(-vector[1], Math.sqrt((vector[0] * vector[0]) + (vector[2] * vector[2]))); 95 | } 96 | 97 | function compute_vertex_normals(position) { 98 | const cb = new Float32Array(3); 99 | const ab = new Float32Array(3); 100 | 101 | const normals = new Float32Array(position.length); 102 | 103 | for (let i = 0; i < position.length; i += 9 ) { 104 | const pa = position.subarray(i + 0, i + 3); 105 | const pb = position.subarray(i + 3, i + 6); 106 | const pc = position.subarray(i + 6, i + 9); 107 | 108 | set3(cb, pc[0] - pb[0], pc[1] - pb[1], pc[2] - pb[2]); 109 | set3(ab, pa[0] - pb[0], pa[1] - pb[1], pa[2] - pb[2]); 110 | 111 | // cb x ab 112 | const x = cb[1] * ab[2] - cb[2] * ab[1]; 113 | const y = cb[2] * ab[0] - cb[0] * ab[2]; 114 | const z = cb[0] * ab[1] - cb[1] * ab[0]; 115 | 116 | normals[i + 0] = normals[i + 3] = normals[i + 6] = x; 117 | normals[i + 1] = normals[i + 4] = normals[i + 7] = y; 118 | normals[i + 2] = normals[i + 5] = normals[i + 8] = z; 119 | } 120 | 121 | return normals; 122 | } 123 | 124 | function create_vertex_buffer(vertices, indices, subdivisions) { 125 | const vertex_buffer = []; 126 | 127 | const a = new Float32Array(3); 128 | const b = new Float32Array(3); 129 | const c = new Float32Array(3); 130 | 131 | for (let i = 0; i < indices.length; i += 3) { 132 | // get the vertices of the face 133 | get_vertex_data(indices[i + 0], a); 134 | get_vertex_data(indices[i + 1], b); 135 | get_vertex_data(indices[i + 2], c); 136 | 137 | // perform subdivision 138 | subdivide_face(a, b, c, subdivisions); 139 | } 140 | 141 | function get_vertex_data(index, out) { 142 | const offset = index * 3; 143 | 144 | out[0] = vertices[offset + 0]; 145 | out[1] = vertices[offset + 1]; 146 | out[2] = vertices[offset + 2]; 147 | } 148 | 149 | function push_vertex(vertex) { 150 | vertex_buffer.push(vertex[0], vertex[1], vertex[2]); 151 | } 152 | 153 | function subdivide_face(a, b, c, subdivisions) { 154 | const cols = Math.pow(2, subdivisions); 155 | 156 | // we use this multidimensional array as a data structure for creating the subdivision 157 | const v = []; 158 | 159 | // construct all of the vertices for this subdivision 160 | for (let i = 0; i <= cols; i++) { 161 | v[i] = []; 162 | 163 | const aj = lerp(a, c, i / cols); 164 | const bj = lerp(b, c, i / cols); 165 | 166 | const rows = cols - i; 167 | 168 | for (let j = 0; j <= rows; j++) { 169 | if (j === 0 && i === cols) { 170 | v[i][j] = aj; 171 | } else { 172 | v[i][j] = lerp(aj, bj, j / rows); 173 | } 174 | } 175 | } 176 | 177 | // construct all of the faces 178 | for (let i = 0; i < cols; i++) { 179 | for (let j = 0; j < 2 * (cols - i) - 1; j++) { 180 | const k = Math.floor(j / 2); 181 | 182 | if (j % 2 === 0) { 183 | push_vertex(v[i][k + 1]); 184 | push_vertex(v[i + 1][k]); 185 | push_vertex(v[i][k]); 186 | } else { 187 | push_vertex(v[i][k + 1]); 188 | push_vertex(v[i + 1][k + 1]); 189 | push_vertex(v[i + 1][k]); 190 | } 191 | } 192 | } 193 | } 194 | 195 | return new Float32Array(vertex_buffer); 196 | } 197 | 198 | export default memoize((vertices, indices, subdivisions = 0, shading = 'flat') => { 199 | var uv_buffer = []; 200 | 201 | // the subdivision creates the vertex buffer data 202 | const vertex_buffer = create_vertex_buffer(vertices, indices, subdivisions); 203 | 204 | for (let i = 0; i < vertex_buffer.length; i += 3) { 205 | const vertex = new Float32Array(vertex_buffer.buffer, i * 4, 3); 206 | 207 | // all vertices should lie on a conceptual sphere with a given radius 208 | normalize(vertex); 209 | 210 | var u = azimuth(vertex) / 2 / Math.PI + 0.5; 211 | var v = inclination(vertex) / Math.PI + 0.5; 212 | uv_buffer.push(u, 1 - v); 213 | } 214 | 215 | correct_uvs(vertex_buffer, uv_buffer); 216 | correct_seam(uv_buffer); 217 | 218 | const position_buffer = new Float32Array(vertex_buffer); 219 | 220 | return new Geometry({ 221 | position: { 222 | data: position_buffer, 223 | size: 3 224 | }, 225 | 226 | normal: { 227 | data: shading === 'smooth' ? position_buffer : compute_vertex_normals(position_buffer), 228 | size: 3 229 | }, 230 | 231 | uv: { 232 | data: new Float32Array(uv_buffer), 233 | size: 2 234 | } 235 | }); 236 | }); -------------------------------------------------------------------------------- /controls/OrbitControls.svelte: -------------------------------------------------------------------------------- 1 | 277 | 278 | -------------------------------------------------------------------------------- /scene/Scene.svelte: -------------------------------------------------------------------------------- 1 | 47 | 48 | 411 | 412 | 421 | 422 |
423 | 424 | 425 | {#if gl} 426 | 427 | {/if} 428 |
-------------------------------------------------------------------------------- /internal/constants.mjs: -------------------------------------------------------------------------------- 1 | // https://www.khronos.org/registry/webgl/specs/1.0/ 2 | 3 | /* ClearBufferMask */ 4 | export const DEPTH_BUFFER_BIT = 0x00000100; 5 | export const STENCIL_BUFFER_BIT = 0x00000400; 6 | export const COLOR_BUFFER_BIT = 0x00004000; 7 | 8 | /* BeginMode */ 9 | export const POINTS = 0x0000; 10 | export const LINES = 0x0001; 11 | export const LINE_LOOP = 0x0002; 12 | export const LINE_STRIP = 0x0003; 13 | export const TRIANGLES = 0x0004; 14 | export const TRIANGLE_STRIP = 0x0005; 15 | export const TRIANGLE_FAN = 0x0006; 16 | 17 | /* BlendingFactorDest */ 18 | export const ZERO = 0; 19 | export const ONE = 1; 20 | export const SRC_COLOR = 0x0300; 21 | export const ONE_MINUS_SRC_COLOR = 0x0301; 22 | export const SRC_ALPHA = 0x0302; 23 | export const ONE_MINUS_SRC_ALPHA = 0x0303; 24 | export const DST_ALPHA = 0x0304; 25 | export const ONE_MINUS_DST_ALPHA = 0x0305; 26 | 27 | /* BlendingFactorSrc */ 28 | export const DST_COLOR = 0x0306; 29 | export const ONE_MINUS_DST_COLOR = 0x0307; 30 | export const SRC_ALPHA_SATURATE = 0x0308; 31 | 32 | /* BlendEquationSeparate */ 33 | export const FUNC_ADD = 0x8006; 34 | export const BLEND_EQUATION = 0x8009; 35 | export const BLEND_EQUATION_RGB = 0x8009; /* same as BLEND_EQUATION */ 36 | export const BLEND_EQUATION_ALPHA = 0x883D; 37 | 38 | /* BlendSubtract */ 39 | export const FUNC_SUBTRACT = 0x800A; 40 | export const FUNC_REVERSE_SUBTRACT = 0x800B; 41 | 42 | /* Separate Blend Functions */ 43 | export const BLEND_DST_RGB = 0x80C8; 44 | export const BLEND_SRC_RGB = 0x80C9; 45 | export const BLEND_DST_ALPHA = 0x80CA; 46 | export const BLEND_SRC_ALPHA = 0x80CB; 47 | export const CONSTANT_COLOR = 0x8001; 48 | export const ONE_MINUS_CONSTANT_COLOR = 0x8002; 49 | export const CONSTANT_ALPHA = 0x8003; 50 | export const ONE_MINUS_CONSTANT_ALPHA = 0x8004; 51 | export const BLEND_COLOR = 0x8005; 52 | 53 | /* Buffer Objects */ 54 | export const ARRAY_BUFFER = 0x8892; 55 | export const ELEMENT_ARRAY_BUFFER = 0x8893; 56 | export const ARRAY_BUFFER_BINDING = 0x8894; 57 | export const ELEMENT_ARRAY_BUFFER_BINDING = 0x8895; 58 | 59 | export const STREAM_DRAW = 0x88E0; 60 | export const STATIC_DRAW = 0x88E4; 61 | export const DYNAMIC_DRAW = 0x88E8; 62 | 63 | export const BUFFER_SIZE = 0x8764; 64 | export const BUFFER_USAGE = 0x8765; 65 | 66 | export const CURRENT_VERTEX_ATTRIB = 0x8626; 67 | 68 | /* CullFaceMode */ 69 | export const FRONT = 0x0404; 70 | export const BACK = 0x0405; 71 | export const FRONT_AND_BACK = 0x0408; 72 | 73 | /* EnableCap */ 74 | /* TEXTURE_2D */ 75 | export const CULL_FACE = 0x0B44; 76 | export const BLEND = 0x0BE2; 77 | export const DITHER = 0x0BD0; 78 | export const STENCIL_TEST = 0x0B90; 79 | export const DEPTH_TEST = 0x0B71; 80 | export const SCISSOR_TEST = 0x0C11; 81 | export const POLYGON_OFFSET_FILL = 0x8037; 82 | export const SAMPLE_ALPHA_TO_COVERAGE = 0x809E; 83 | export const SAMPLE_COVERAGE = 0x80A0; 84 | 85 | /* ErrorCode */ 86 | export const NO_ERROR = 0; 87 | export const INVALID_ENUM = 0x0500; 88 | export const INVALID_VALUE = 0x0501; 89 | export const INVALID_OPERATION = 0x0502; 90 | export const OUT_OF_MEMORY = 0x0505; 91 | 92 | /* FrontFaceDirection */ 93 | export const CW = 0x0900; 94 | export const CCW = 0x0901; 95 | 96 | /* GetPName */ 97 | export const LINE_WIDTH = 0x0B21; 98 | export const ALIASED_POINT_SIZE_RANGE = 0x846D; 99 | export const ALIASED_LINE_WIDTH_RANGE = 0x846E; 100 | export const CULL_FACE_MODE = 0x0B45; 101 | export const FRONT_FACE = 0x0B46; 102 | export const DEPTH_RANGE = 0x0B70; 103 | export const DEPTH_WRITEMASK = 0x0B72; 104 | export const DEPTH_CLEAR_VALUE = 0x0B73; 105 | export const DEPTH_FUNC = 0x0B74; 106 | export const STENCIL_CLEAR_VALUE = 0x0B91; 107 | export const STENCIL_FUNC = 0x0B92; 108 | export const STENCIL_FAIL = 0x0B94; 109 | export const STENCIL_PASS_DEPTH_FAIL = 0x0B95; 110 | export const STENCIL_PASS_DEPTH_PASS = 0x0B96; 111 | export const STENCIL_REF = 0x0B97; 112 | export const STENCIL_VALUE_MASK = 0x0B93; 113 | export const STENCIL_WRITEMASK = 0x0B98; 114 | export const STENCIL_BACK_FUNC = 0x8800; 115 | export const STENCIL_BACK_FAIL = 0x8801; 116 | export const STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802; 117 | export const STENCIL_BACK_PASS_DEPTH_PASS = 0x8803; 118 | export const STENCIL_BACK_REF = 0x8CA3; 119 | export const STENCIL_BACK_VALUE_MASK = 0x8CA4; 120 | export const STENCIL_BACK_WRITEMASK = 0x8CA5; 121 | export const VIEWPORT = 0x0BA2; 122 | export const SCISSOR_BOX = 0x0C10; 123 | /* SCISSOR_TEST */ 124 | export const COLOR_CLEAR_VALUE = 0x0C22; 125 | export const COLOR_WRITEMASK = 0x0C23; 126 | export const UNPACK_ALIGNMENT = 0x0CF5; 127 | export const PACK_ALIGNMENT = 0x0D05; 128 | export const MAX_TEXTURE_SIZE = 0x0D33; 129 | export const MAX_VIEWPORT_DIMS = 0x0D3A; 130 | export const SUBPIXEL_BITS = 0x0D50; 131 | export const RED_BITS = 0x0D52; 132 | export const GREEN_BITS = 0x0D53; 133 | export const BLUE_BITS = 0x0D54; 134 | export const ALPHA_BITS = 0x0D55; 135 | export const DEPTH_BITS = 0x0D56; 136 | export const STENCIL_BITS = 0x0D57; 137 | export const POLYGON_OFFSET_UNITS = 0x2A00; 138 | /* POLYGON_OFFSET_FILL */ 139 | export const POLYGON_OFFSET_FACTOR = 0x8038; 140 | export const TEXTURE_BINDING_2D = 0x8069; 141 | export const SAMPLE_BUFFERS = 0x80A8; 142 | export const SAMPLES = 0x80A9; 143 | export const SAMPLE_COVERAGE_VALUE = 0x80AA; 144 | export const SAMPLE_COVERAGE_INVERT = 0x80AB; 145 | 146 | export const COMPRESSED_TEXTURE_FORMATS = 0x86A3; 147 | 148 | /* HintMode */ 149 | export const DONT_CARE = 0x1100; 150 | export const FASTEST = 0x1101; 151 | export const NICEST = 0x1102; 152 | 153 | /* HintTarget */ 154 | export const GENERATE_MIPMAP_HINT = 0x8192; 155 | 156 | /* DataType */ 157 | export const BYTE = 0x1400; 158 | export const UNSIGNED_BYTE = 0x1401; 159 | export const SHORT = 0x1402; 160 | export const UNSIGNED_SHORT = 0x1403; 161 | export const INT = 0x1404; 162 | export const UNSIGNED_INT = 0x1405; 163 | export const FLOAT = 0x1406; 164 | 165 | /* PixelFormat */ 166 | export const DEPTH_COMPONENT = 0x1902; 167 | export const ALPHA = 0x1906; 168 | export const RGB = 0x1907; 169 | export const RGBA = 0x1908; 170 | export const LUMINANCE = 0x1909; 171 | export const LUMINANCE_ALPHA = 0x190A; 172 | 173 | /* PixelType */ 174 | /* UNSIGNED_BYTE */ 175 | export const UNSIGNED_SHORT_4_4_4_4 = 0x8033; 176 | export const UNSIGNED_SHORT_5_5_5_1 = 0x8034; 177 | export const UNSIGNED_SHORT_5_6_5 = 0x8363; 178 | 179 | /* Shaders */ 180 | export const FRAGMENT_SHADER = 0x8B30; 181 | export const VERTEX_SHADER = 0x8B31; 182 | export const MAX_VERTEX_ATTRIBS = 0x8869; 183 | export const MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB; 184 | export const MAX_VARYING_VECTORS = 0x8DFC; 185 | export const MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D; 186 | export const MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C; 187 | export const MAX_TEXTURE_IMAGE_UNITS = 0x8872; 188 | export const MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD; 189 | export const SHADER_TYPE = 0x8B4F; 190 | export const DELETE_STATUS = 0x8B80; 191 | export const LINK_STATUS = 0x8B82; 192 | export const VALIDATE_STATUS = 0x8B83; 193 | export const ATTACHED_SHADERS = 0x8B85; 194 | export const ACTIVE_UNIFORMS = 0x8B86; 195 | export const ACTIVE_ATTRIBUTES = 0x8B89; 196 | export const SHADING_LANGUAGE_VERSION = 0x8B8C; 197 | export const CURRENT_PROGRAM = 0x8B8D; 198 | 199 | /* StencilFunction */ 200 | export const NEVER = 0x0200; 201 | export const LESS = 0x0201; 202 | export const EQUAL = 0x0202; 203 | export const LEQUAL = 0x0203; 204 | export const GREATER = 0x0204; 205 | export const NOTEQUAL = 0x0205; 206 | export const GEQUAL = 0x0206; 207 | export const ALWAYS = 0x0207; 208 | 209 | /* StencilOp */ 210 | export const KEEP = 0x1E00; 211 | export const REPLACE = 0x1E01; 212 | export const INCR = 0x1E02; 213 | export const DECR = 0x1E03; 214 | export const INVERT = 0x150A; 215 | export const INCR_WRAP = 0x8507; 216 | export const DECR_WRAP = 0x8508; 217 | 218 | /* StringName */ 219 | export const VENDOR = 0x1F00; 220 | export const RENDERER = 0x1F01; 221 | export const VERSION = 0x1F02; 222 | 223 | /* TextureMagFilter */ 224 | export const NEAREST = 0x2600; 225 | export const LINEAR = 0x2601; 226 | 227 | /* TextureMinFilter */ 228 | export const NEAREST_MIPMAP_NEAREST = 0x2700; 229 | export const LINEAR_MIPMAP_NEAREST = 0x2701; 230 | export const NEAREST_MIPMAP_LINEAR = 0x2702; 231 | export const LINEAR_MIPMAP_LINEAR = 0x2703; 232 | 233 | /* TextureParameterName */ 234 | export const TEXTURE_MAG_FILTER = 0x2800; 235 | export const TEXTURE_MIN_FILTER = 0x2801; 236 | export const TEXTURE_WRAP_S = 0x2802; 237 | export const TEXTURE_WRAP_T = 0x2803; 238 | 239 | /* TextureTarget */ 240 | export const TEXTURE_2D = 0x0DE1; 241 | export const TEXTURE = 0x1702; 242 | 243 | export const TEXTURE_CUBE_MAP = 0x8513; 244 | export const TEXTURE_BINDING_CUBE_MAP = 0x8514; 245 | export const TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; 246 | export const TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; 247 | export const TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; 248 | export const TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; 249 | export const TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; 250 | export const TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; 251 | export const MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C; 252 | 253 | /* TextureUnit */ 254 | export const TEXTURE0 = 0x84C0; 255 | export const TEXTURE1 = 0x84C1; 256 | export const TEXTURE2 = 0x84C2; 257 | export const TEXTURE3 = 0x84C3; 258 | export const TEXTURE4 = 0x84C4; 259 | export const TEXTURE5 = 0x84C5; 260 | export const TEXTURE6 = 0x84C6; 261 | export const TEXTURE7 = 0x84C7; 262 | export const TEXTURE8 = 0x84C8; 263 | export const TEXTURE9 = 0x84C9; 264 | export const TEXTURE10 = 0x84CA; 265 | export const TEXTURE11 = 0x84CB; 266 | export const TEXTURE12 = 0x84CC; 267 | export const TEXTURE13 = 0x84CD; 268 | export const TEXTURE14 = 0x84CE; 269 | export const TEXTURE15 = 0x84CF; 270 | export const TEXTURE16 = 0x84D0; 271 | export const TEXTURE17 = 0x84D1; 272 | export const TEXTURE18 = 0x84D2; 273 | export const TEXTURE19 = 0x84D3; 274 | export const TEXTURE20 = 0x84D4; 275 | export const TEXTURE21 = 0x84D5; 276 | export const TEXTURE22 = 0x84D6; 277 | export const TEXTURE23 = 0x84D7; 278 | export const TEXTURE24 = 0x84D8; 279 | export const TEXTURE25 = 0x84D9; 280 | export const TEXTURE26 = 0x84DA; 281 | export const TEXTURE27 = 0x84DB; 282 | export const TEXTURE28 = 0x84DC; 283 | export const TEXTURE29 = 0x84DD; 284 | export const TEXTURE30 = 0x84DE; 285 | export const TEXTURE31 = 0x84DF; 286 | export const ACTIVE_TEXTURE = 0x84E0; 287 | 288 | /* TextureWrapMode */ 289 | export const REPEAT = 0x2901; 290 | export const CLAMP_TO_EDGE = 0x812F; 291 | export const MIRRORED_REPEAT = 0x8370; 292 | 293 | /* Uniform Types */ 294 | export const FLOAT_VEC2 = 0x8B50; 295 | export const FLOAT_VEC3 = 0x8B51; 296 | export const FLOAT_VEC4 = 0x8B52; 297 | export const INT_VEC2 = 0x8B53; 298 | export const INT_VEC3 = 0x8B54; 299 | export const INT_VEC4 = 0x8B55; 300 | export const BOOL = 0x8B56; 301 | export const BOOL_VEC2 = 0x8B57; 302 | export const BOOL_VEC3 = 0x8B58; 303 | export const BOOL_VEC4 = 0x8B59; 304 | export const FLOAT_MAT2 = 0x8B5A; 305 | export const FLOAT_MAT3 = 0x8B5B; 306 | export const FLOAT_MAT4 = 0x8B5C; 307 | export const SAMPLER_2D = 0x8B5E; 308 | export const SAMPLER_CUBE = 0x8B60; 309 | 310 | /* Vertex Arrays */ 311 | export const VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622; 312 | export const VERTEX_ATTRIB_ARRAY_SIZE = 0x8623; 313 | export const VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624; 314 | export const VERTEX_ATTRIB_ARRAY_TYPE = 0x8625; 315 | export const VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A; 316 | export const VERTEX_ATTRIB_ARRAY_POINTER = 0x8645; 317 | export const VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F; 318 | 319 | /* Read Format */ 320 | export const IMPLEMENTATION_COLOR_READ_TYPE = 0x8B9A; 321 | export const IMPLEMENTATION_COLOR_READ_FORMAT = 0x8B9B; 322 | 323 | /* Shader Source */ 324 | export const COMPILE_STATUS = 0x8B81; 325 | 326 | /* Shader Precision-Specified Types */ 327 | export const LOW_FLOAT = 0x8DF0; 328 | export const MEDIUM_FLOAT = 0x8DF1; 329 | export const HIGH_FLOAT = 0x8DF2; 330 | export const LOW_INT = 0x8DF3; 331 | export const MEDIUM_INT = 0x8DF4; 332 | export const HIGH_INT = 0x8DF5; 333 | 334 | /* Framebuffer Object. */ 335 | export const FRAMEBUFFER = 0x8D40; 336 | export const RENDERBUFFER = 0x8D41; 337 | 338 | export const RGBA4 = 0x8056; 339 | export const RGB5_A1 = 0x8057; 340 | export const RGB565 = 0x8D62; 341 | export const DEPTH_COMPONENT16 = 0x81A5; 342 | export const STENCIL_INDEX = 0x1901; 343 | export const STENCIL_INDEX8 = 0x8D48; 344 | export const DEPTH_STENCIL = 0x84F9; 345 | 346 | export const RENDERBUFFER_WIDTH = 0x8D42; 347 | export const RENDERBUFFER_HEIGHT = 0x8D43; 348 | export const RENDERBUFFER_INTERNAL_FORMAT = 0x8D44; 349 | export const RENDERBUFFER_RED_SIZE = 0x8D50; 350 | export const RENDERBUFFER_GREEN_SIZE = 0x8D51; 351 | export const RENDERBUFFER_BLUE_SIZE = 0x8D52; 352 | export const RENDERBUFFER_ALPHA_SIZE = 0x8D53; 353 | export const RENDERBUFFER_DEPTH_SIZE = 0x8D54; 354 | export const RENDERBUFFER_STENCIL_SIZE = 0x8D55; 355 | 356 | export const FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0; 357 | export const FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1; 358 | export const FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2; 359 | export const FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3; 360 | 361 | export const COLOR_ATTACHMENT0 = 0x8CE0; 362 | export const DEPTH_ATTACHMENT = 0x8D00; 363 | export const STENCIL_ATTACHMENT = 0x8D20; 364 | export const DEPTH_STENCIL_ATTACHMENT = 0x821A; 365 | 366 | export const NONE = 0; 367 | 368 | export const FRAMEBUFFER_COMPLETE = 0x8CD5; 369 | export const FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6; 370 | export const FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7; 371 | export const FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9; 372 | export const FRAMEBUFFER_UNSUPPORTED = 0x8CDD; 373 | 374 | export const FRAMEBUFFER_BINDING = 0x8CA6; 375 | export const RENDERBUFFER_BINDING = 0x8CA7; 376 | export const MAX_RENDERBUFFER_SIZE = 0x84E8; 377 | 378 | export const INVALID_FRAMEBUFFER_OPERATION = 0x0506; 379 | 380 | /* WebGL-specific enums */ 381 | export const UNPACK_FLIP_Y_WEBGL = 0x9240; 382 | export const UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241; 383 | export const CONTEXT_LOST_WEBGL = 0x9242; 384 | export const UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243; 385 | export const BROWSER_DEFAULT_WEBGL = 0x9244; --------------------------------------------------------------------------------