├── img └── jellyfish.jpg ├── documentation └── assets │ └── img │ └── icon.png ├── src ├── shaders │ ├── picking │ │ ├── fragment.glsl │ │ └── vertex.glsl │ └── primitive │ │ ├── fragment.glsl │ │ └── vertex.glsl ├── js │ ├── modules │ │ ├── Volume.js │ │ ├── Vector.js │ │ ├── Geometry.js │ │ ├── Program.js │ │ ├── Circle.js │ │ ├── FrameBuffer.js │ │ ├── Tetrahedron.js │ │ ├── Sandbox.js │ │ ├── Plane.js │ │ ├── Collection.js │ │ ├── Texture.js │ │ ├── Sphere.js │ │ ├── Cylinder.js │ │ ├── Orthographic.js │ │ ├── Light.js │ │ ├── Perspective.js │ │ ├── ColorPicker.js │ │ ├── Cube.js │ │ ├── Renderer.js │ │ ├── Mesh.js │ │ └── Matrix.js │ └── demo │ │ └── main.js └── css │ └── main.css ├── LICENSE.md ├── README.md ├── index.html └── dist └── js ├── sandbox.min.js └── demo.js /img/jellyfish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cherupil/Sandbox/HEAD/img/jellyfish.jpg -------------------------------------------------------------------------------- /documentation/assets/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cherupil/Sandbox/HEAD/documentation/assets/img/icon.png -------------------------------------------------------------------------------- /src/shaders/picking/fragment.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uTexture; 4 | uniform vec3 uPlaneColor; 5 | uniform float uPickingColor; 6 | 7 | varying vec2 vUV; 8 | 9 | void main() { 10 | vec4 uvs = vec4(vUV, 0.0, 1.0); 11 | gl_FragColor = vec4(1.0, 0.0, 0.0, uPickingColor); 12 | } -------------------------------------------------------------------------------- /src/shaders/primitive/fragment.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec3 vNormal; 4 | varying vec2 vUV; 5 | varying vec3 vPos; 6 | 7 | void main() { 8 | float distance = 0.25 + (1.0 - 0.25) * (vPos.z - (-1.0)) / (1.0 - (-1.0)); 9 | vec3 green = vec3(0.420, 0.831, 0.565) * distance; 10 | 11 | gl_FragColor = vec4(green, 1.0); 12 | } -------------------------------------------------------------------------------- /src/js/modules/Volume.js: -------------------------------------------------------------------------------- 1 | export default class Volume { 2 | constructor() { 3 | this.objects = [] 4 | } 5 | 6 | add(object) { 7 | this.objects.push(object) 8 | this.objects.sort((a, b) => { 9 | const bufferDiff = a.geometryID - b.geometryID 10 | if (bufferDiff) { 11 | return bufferDiff 12 | } 13 | return a.shader.id - b.shader.id 14 | }) 15 | } 16 | } -------------------------------------------------------------------------------- /src/shaders/picking/vertex.glsl: -------------------------------------------------------------------------------- 1 | attribute vec4 aPosition; 2 | attribute vec3 aNormal; 3 | attribute vec2 aUV; 4 | 5 | uniform mat4 uViewProjectionMatrix; 6 | uniform mat4 uNormalMatrix; 7 | uniform mat4 uLocalMatrix; 8 | uniform float uTime; 9 | 10 | varying vec3 vNormal; 11 | varying vec2 vUV; 12 | 13 | void main() { 14 | vec4 position = uViewProjectionMatrix * uLocalMatrix * aPosition; 15 | gl_Position = position; 16 | vNormal = aNormal; 17 | vUV = aUV; 18 | } -------------------------------------------------------------------------------- /src/shaders/primitive/vertex.glsl: -------------------------------------------------------------------------------- 1 | attribute vec4 aPosition; 2 | attribute vec3 aNormal; 3 | attribute vec2 aUV; 4 | 5 | uniform mat4 uViewProjectionMatrix; 6 | uniform mat4 uNormalMatrix; 7 | uniform mat4 uLocalMatrix; 8 | 9 | varying vec3 vNormal; 10 | varying vec2 vUV; 11 | varying vec3 vPos; 12 | 13 | void main() { 14 | vec4 position = uViewProjectionMatrix * uLocalMatrix * aPosition; 15 | gl_Position = position; 16 | vNormal = aNormal + 0.5; 17 | vUV = aUV; 18 | vPos = aPosition.xyz; 19 | } -------------------------------------------------------------------------------- /src/js/modules/Vector.js: -------------------------------------------------------------------------------- 1 | export default class Vector { 2 | static cross(a, b) { 3 | return [ 4 | a[1] * b[2] - a[2] * b[1], 5 | a[2] * b[0] - a[0] * b[2], 6 | a[0] * b[1] - a[1] * b[0] 7 | ] 8 | } 9 | 10 | static subtract(a, b) { 11 | return [ 12 | a[0] - b[0], 13 | a[1] - b[1], 14 | a[2] - b[2] 15 | ] 16 | } 17 | 18 | static normalize(v) { 19 | const magnitude = Math.sqrt((v[0]**2) + (v[1]**2) + (v[2]**2)) 20 | 21 | if (magnitude > 0.00001) { 22 | return [ 23 | v[0] / magnitude, 24 | v[1] / magnitude, 25 | v[2] / magnitude 26 | ] 27 | } else { 28 | return [ 29 | 0, 30 | 0, 31 | 0 32 | ] 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/js/modules/Geometry.js: -------------------------------------------------------------------------------- 1 | let geometryId = 0 2 | 3 | export default class Geometry { 4 | constructor(positions) { 5 | this.id = geometryId++ 6 | this.attributes = {} 7 | this.setAttribute('aPosition', new Float32Array(positions), 3) 8 | this._generateNormals(positions) 9 | } 10 | 11 | setAttribute(name, data, size) { 12 | this.attributes[name] = { 13 | name, 14 | data, 15 | size, 16 | count: data.length / size 17 | } 18 | } 19 | 20 | _generateNormals(positions) { 21 | const normals = [] 22 | for (var i = 0; i < positions.length; i+=3) { 23 | const x = positions[i] 24 | const y = positions[i + 1] 25 | const z = positions[i + 2] 26 | const magnitude = Math.sqrt((x**2) + (y**2) + (z**2)) 27 | normals.push((x/magnitude), (y/magnitude), (z/magnitude)) 28 | } 29 | this.setAttribute('aNormal', new Float32Array(normals), 3) 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Christopher Cherupil 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/js/modules/Program.js: -------------------------------------------------------------------------------- 1 | 2 | let programId = 0 3 | 4 | export default class Program { 5 | constructor(gl, vertex, fragment) { 6 | const vertexShader = this._createShader(gl, gl.VERTEX_SHADER, vertex) 7 | const fragmentShader = this._createShader(gl, gl.FRAGMENT_SHADER, fragment) 8 | this.gl = gl 9 | this.id = programId++ 10 | this.program = this._createProgram(gl, vertexShader, fragmentShader) 11 | } 12 | 13 | _createShader(gl, type, source) { 14 | const shader = gl.createShader(type) 15 | gl.shaderSource(shader, source) 16 | gl.compileShader(shader) 17 | const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) 18 | if (success) { 19 | return shader 20 | } 21 | 22 | console.log(gl.getShaderInfoLog(shader)) 23 | gl.deleteShader(shader) 24 | } 25 | 26 | _createProgram(gl, vertexShader, fragmentShader) { 27 | const program = gl.createProgram() 28 | gl.attachShader(program, vertexShader) 29 | gl.attachShader(program, fragmentShader) 30 | gl.linkProgram(program) 31 | const success = gl.getProgramParameter(program, gl.LINK_STATUS) 32 | if (success) { 33 | return program 34 | } 35 | 36 | console.log(gl.getProgramInfoLog(program)) 37 | gl.deleteProgram(program) 38 | } 39 | } -------------------------------------------------------------------------------- /src/js/modules/Circle.js: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry' 2 | 3 | export default class Circle extends Geometry { 4 | constructor(radius, segments) { 5 | const positions = [] 6 | 7 | positions.push(0, 0, 0) 8 | 9 | for (let i = 0; i < segments; i++) { 10 | const x = Math.cos(i * Math.PI / (segments / 2)) * radius 11 | const y = Math.sin(i * Math.PI / (segments / 2)) * radius 12 | const z = 0 13 | positions.push(x, y, z) 14 | } 15 | 16 | positions.push(Math.cos(0) * radius, Math.sin(0) * radius, 0) 17 | 18 | super(positions) 19 | 20 | const normals = [] 21 | for (var i = 0; i < positions.length; i+=3) { 22 | const x = positions[i] 23 | const y = positions[i + 1] 24 | const z = 1 25 | const magnitude = Math.sqrt((x**2) + (y**2) + (z**2)) 26 | normals.push((x/magnitude), (y/magnitude), (z/magnitude)) 27 | } 28 | this.attributes.aNormal.data = new Float32Array(normals) 29 | this.attributes.aNormal.count = normals.length / 3 30 | 31 | const uvs = [] 32 | for (let i = 0; i < positions.length; i+=3) { 33 | const x = (positions[i] + radius) / (radius * 2) 34 | const y = (positions[i+1] + radius) / (radius * 2) 35 | uvs.push(x, y) 36 | } 37 | this.setAttribute('aUV', new Float32Array(uvs), 2) 38 | 39 | this.type = 'TRIANGLE_FAN' 40 | } 41 | } -------------------------------------------------------------------------------- /src/js/modules/FrameBuffer.js: -------------------------------------------------------------------------------- 1 | export default class FrameBuffer { 2 | constructor(gl, target) { 3 | this.gl = gl 4 | this.target = target 5 | 6 | this.buffer = this.gl.createFramebuffer() 7 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffer) 8 | 9 | this.depthBuffer = this.gl.createRenderbuffer() 10 | this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.depthBuffer) 11 | this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, this.target.width, this.target.height) 12 | 13 | this.attachTexture(this.target) 14 | this.attachRenderBuffer() 15 | } 16 | 17 | resize(width, height) { 18 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.target.texture) 19 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.target.format, width, height, 0, this.target.format, this.gl.UNSIGNED_BYTE, 20 | null) 21 | this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.depthBuffer) 22 | this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, width, height) 23 | } 24 | 25 | attachTexture(target) { 26 | this.target = target 27 | this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, this.target.texture, 0) 28 | } 29 | 30 | attachRenderBuffer() { 31 | this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, this.depthBuffer) 32 | } 33 | } -------------------------------------------------------------------------------- /src/js/modules/Tetrahedron.js: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry' 2 | 3 | export default class Tetra extends Geometry { 4 | constructor(size) { 5 | const positions = [] 6 | 7 | const height = (Math.sqrt(3) / 2) * size 8 | 9 | positions.push( 10 | -size/2, -(size * Math.sqrt(3)) / 6, (size * Math.sqrt(3)) / 6, 11 | size/2, -(size * Math.sqrt(3)) / 6, (size * Math.sqrt(3)) / 6, 12 | 0, (size * Math.sqrt(3)) / 3, 0, 13 | 14 | size/2, -(size * Math.sqrt(3)) / 6, (size * Math.sqrt(3)) / 6, 15 | 0, -(size * Math.sqrt(3)) / 6, -(size * Math.sqrt(3)) / 3, 16 | 0, (size * Math.sqrt(3)) / 3, 0, 17 | 18 | 0, -(size * Math.sqrt(3)) / 6, -(size * Math.sqrt(3)) / 3, 19 | -size/2, -(size * Math.sqrt(3)) / 6, (size * Math.sqrt(3)) / 6, 20 | 0, (size * Math.sqrt(3)) / 3, 0, 21 | 22 | -size/2, -(size * Math.sqrt(3)) / 6, (size * Math.sqrt(3)) / 6, 23 | 0, -(size * Math.sqrt(3)) / 6, -(size * Math.sqrt(3)) / 3, 24 | size/2, -(size * Math.sqrt(3)) / 6, (size * Math.sqrt(3)) / 6 25 | ) 26 | 27 | super(positions) 28 | 29 | const uvs = [] 30 | 31 | for (let i = 0; i < positions.length; i+=9) { 32 | if (i === 27) { 33 | uvs.push(1, (1 - Math.sqrt(0.75)) / 2, 0.5, ((1 - Math.sqrt(0.75)) / 2) + Math.sqrt(0.75), 0, (1 - Math.sqrt(0.75)) / 2) 34 | } else { 35 | uvs.push(0, (1 - Math.sqrt(0.75)) / 2, 1, (1 - Math.sqrt(0.75)) / 2, 0.5, ((1 - Math.sqrt(0.75)) / 2) + Math.sqrt(0.75)) 36 | } 37 | } 38 | 39 | this.setAttribute('aUV', new Float32Array(uvs), 2) 40 | } 41 | } -------------------------------------------------------------------------------- /src/js/modules/Sandbox.js: -------------------------------------------------------------------------------- 1 | import Renderer from './Renderer.js' 2 | import Orthographic from './Orthographic.js' 3 | import Perspective from './Perspective.js' 4 | import Volume from './Volume.js' 5 | import Collection from './Collection.js' 6 | import Mesh from './Mesh.js' 7 | import Geometry from './Geometry.js' 8 | import Plane from './Plane.js' 9 | import Circle from './Circle.js' 10 | import Tetrahedron from './Tetrahedron.js' 11 | import Cube from './Cube.js' 12 | import Sphere from './Sphere.js' 13 | import Cylinder from './Cylinder.js' 14 | import Program from './Program.js' 15 | import { ImageTexture, DataTexture } from './Texture.js' 16 | import Light from './Light.js' 17 | import FrameBuffer from './FrameBuffer.js' 18 | import ColorPicker from './ColorPicker.js' 19 | 20 | export default class Sandbox { 21 | static createColor(r, g, b) { 22 | return { 23 | r: (r / 255), 24 | g: (g / 255), 25 | b: (b / 255) 26 | } 27 | } 28 | } 29 | 30 | Sandbox.Renderer = Renderer 31 | Sandbox.Orthographic = Orthographic 32 | Sandbox.Perspective = Perspective 33 | Sandbox.Volume = Volume 34 | Sandbox.Collection = Collection 35 | Sandbox.Mesh = Mesh 36 | Sandbox.Geometry = Geometry 37 | Sandbox.Plane = Plane 38 | Sandbox.Circle = Circle 39 | Sandbox.Tetrahedron = Tetrahedron 40 | Sandbox.Cube = Cube 41 | Sandbox.Sphere = Sphere 42 | Sandbox.Cylinder = Cylinder 43 | Sandbox.Program = Program 44 | Sandbox.ImageTexture = ImageTexture 45 | Sandbox.DataTexture = DataTexture 46 | Sandbox.Light = Light 47 | Sandbox.FrameBuffer = FrameBuffer 48 | Sandbox.ColorPicker = ColorPicker 49 | -------------------------------------------------------------------------------- /src/js/modules/Plane.js: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry' 2 | 3 | export default class Plane extends Geometry { 4 | constructor(width, height, widthSegments, heightSegments) { 5 | const positions = [] 6 | 7 | const segmentWidth = width / widthSegments 8 | const segmentHeight = height / heightSegments 9 | 10 | for (let i = 0; i < heightSegments; i++) { 11 | for (let j = 0; j < widthSegments; j++) { 12 | const x1 = (j * segmentWidth) - width / 2 13 | const y1 = (i * segmentHeight) - height / 2 14 | const z = 0 15 | 16 | const x2 = ((j + 1) * segmentWidth) - width / 2 17 | const y2 = y1 18 | 19 | const x3 = x1 20 | const y3 = ((i + 1) * segmentHeight) - height / 2 21 | 22 | const x4 = x1 23 | const y4 = y3 24 | 25 | const x5 = x2 26 | const y5 = y2 27 | 28 | const x6 = x2 29 | const y6 = y3 30 | 31 | positions.push(x1, y1, z, x2, y2, z, x3, y3, z, x4, y4, z, x5, y5, z, x6, y6, z) 32 | } 33 | } 34 | super(positions) 35 | 36 | const normals = [] 37 | for (var i = 0; i < positions.length; i+=3) { 38 | const x = positions[i] 39 | const y = positions[i + 1] 40 | const z = 1 41 | const magnitude = Math.sqrt((x**2) + (y**2) + (z**2)) 42 | normals.push((x/magnitude), (y/magnitude), 1) 43 | } 44 | this.attributes.aNormal.data = new Float32Array(normals) 45 | this.attributes.aNormal.count = normals.length / 3 46 | 47 | const uvs = [] 48 | for (let i = 0; i < positions.length; i+=3) { 49 | const x = (positions[i] + width/2) / width 50 | const y = (positions[i+1] + height/2) / height 51 | uvs.push(x, y) 52 | } 53 | this.setAttribute('aUV', new Float32Array(uvs), 2) 54 | } 55 | } -------------------------------------------------------------------------------- /src/js/modules/Collection.js: -------------------------------------------------------------------------------- 1 | import Matrix from './Matrix.js' 2 | 3 | export default class Collection { 4 | constructor() { 5 | this.items = [] 6 | this.position = { 7 | x: 0, 8 | y: 0, 9 | z: 0 10 | } 11 | this.rotation = { 12 | x: 0, 13 | y: 0, 14 | z: 0 15 | } 16 | this.scale = { 17 | x: 1, 18 | y: 1, 19 | z: 1 20 | } 21 | this.localMatrix = Matrix.identity() 22 | } 23 | 24 | _recalculateModelMatrix() { 25 | const identity = Matrix.identity() 26 | const translation = Matrix.translate(this.position.x, this.position.y, this.position.z) 27 | const rotationX = Matrix.rotateX(this.rotation.x) 28 | const rotationY = Matrix.rotateY(this.rotation.y) 29 | const rotationZ = Matrix.rotateZ(this.rotation.z) 30 | const scale = Matrix.scale(this.scale.x, this.scale.y, this.scale.z) 31 | let matrix = Matrix.multiply(identity, translation) 32 | matrix = Matrix.multiply(matrix, rotationX) 33 | matrix = Matrix.multiply(matrix, rotationY) 34 | matrix = Matrix.multiply(matrix, rotationZ) 35 | matrix = Matrix.multiply(matrix, scale) 36 | this.localMatrix = matrix 37 | } 38 | 39 | setProjectionMatrix(matrix) { 40 | this._recalculateModelMatrix() 41 | this.projectionMatrix = matrix 42 | } 43 | 44 | setPosition(x, y, z) { 45 | this.position = { x, y, z } 46 | this._recalculateModelMatrix() 47 | } 48 | 49 | setRotationX(angle) { 50 | this.rotation.x = angle 51 | this._recalculateModelMatrix() 52 | } 53 | 54 | setRotationY(angle) { 55 | this.rotation.y = angle 56 | this._recalculateModelMatrix() 57 | } 58 | 59 | setRotationZ(angle) { 60 | this.rotation.z = angle 61 | this._recalculateModelMatrix() 62 | } 63 | 64 | setScale(x, y, z) { 65 | this.scale = { x, y, z } 66 | this._recalculateModelMatrix() 67 | } 68 | } -------------------------------------------------------------------------------- /src/js/modules/Texture.js: -------------------------------------------------------------------------------- 1 | let textureId = 0 2 | 3 | export class ImageTexture { 4 | constructor(gl, path) { 5 | this.gl = gl 6 | this.texture = this.gl.createTexture() 7 | this.id = textureId++ 8 | 9 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture) 10 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, 1, 1, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, 11 | new Uint8Array([0, 0, 0, 255])) 12 | 13 | this.image = new Image() 14 | this.image.addEventListener('load', this.attachImage.bind(this)) 15 | this.image.src = path 16 | } 17 | 18 | attachImage() { 19 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture) 20 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.image) 21 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE) 22 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE) 23 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR) 24 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR) 25 | } 26 | } 27 | 28 | export class DataTexture { 29 | constructor(gl, format, width, height, data, filter) { 30 | this.gl = gl 31 | this.texture = this.gl.createTexture() 32 | this.id = textureId++ 33 | this.width = width 34 | this.height = height 35 | this.data = data ? new Uint8Array(data) : null 36 | 37 | switch (format) { 38 | case 'rgba': 39 | this.format = this.gl.RGBA 40 | break 41 | case 'rgb': 42 | this.format = this.gl.RGB 43 | break 44 | case 'luminance_alpha': 45 | this.format = this.gl.LUMINANCE_ALPHA 46 | break 47 | case 'luminance': 48 | this.format = this.gl.LUMINANCE 49 | break 50 | default: 51 | this.format = this.gl.RGBA 52 | break 53 | } 54 | 55 | switch (filter) { 56 | case 'linear': 57 | this.filter = this.gl.LINEAR 58 | break 59 | case 'nearest': 60 | this.filter = this.gl.NEAREST 61 | break 62 | default: 63 | this.filter = this.gl.NEAREST 64 | break 65 | } 66 | 67 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture) 68 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.gl.UNSIGNED_BYTE, 69 | this.data) 70 | 71 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE) 72 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE) 73 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.filter) 74 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.filter) 75 | } 76 | } -------------------------------------------------------------------------------- /src/js/modules/Sphere.js: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry' 2 | 3 | export default class Sphere extends Geometry { 4 | constructor(radius, segments) { 5 | const positions = [] 6 | const uvs = [] 7 | 8 | const segmentSize = (Math.PI * 2) / segments 9 | 10 | for (let i = 0; i < segments; i++) { 11 | for (let j = 0; j < segments; j++) { 12 | const x1 = radius * Math.cos(j * segmentSize) * Math.sin(i * segmentSize) 13 | const y1 = radius * Math.cos(i * segmentSize) 14 | const z1 = radius * Math.sin(j * segmentSize) * Math.sin(i * segmentSize) 15 | 16 | const x2 = radius * Math.cos(j * segmentSize) * Math.sin((i + 1) * segmentSize) 17 | const y2 = radius * Math.cos((i + 1) * segmentSize) 18 | const z2 = radius * Math.sin(j * segmentSize) * Math.sin((i + 1) * segmentSize) 19 | 20 | const x3 = radius * Math.cos((j + 1) * segmentSize) * Math.sin((i + 1) * segmentSize) 21 | const y3 = radius * Math.cos((i + 1) * segmentSize) 22 | const z3 = radius * Math.sin((j + 1) * segmentSize) * Math.sin((i + 1) * segmentSize) 23 | 24 | const x4 = x1 25 | const y4 = y1 26 | const z4 = z1 27 | 28 | const x5 = x3 29 | const y5 = y3 30 | const z5 = z3 31 | 32 | const x6 = radius * Math.cos((j + 1) * segmentSize) * Math.sin(i * segmentSize) 33 | const y6 = radius * Math.cos(i * segmentSize) 34 | const z6 = radius * Math.sin((j + 1) * segmentSize) * Math.sin(i * segmentSize) 35 | 36 | positions.push(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, x5, y5, z5, x6, y6, z6) 37 | } 38 | } 39 | 40 | super(positions) 41 | 42 | for (let i = 0; i < this.attributes.aNormal.data.length; i+=3) { 43 | let offset = 0 44 | if ( 45 | (this.attributes.aNormal.data[i+1] == -1) && 46 | (this.attributes.aNormal.data[i] >= 0) 47 | ) { 48 | offset = -0.5 49 | } else if ( 50 | (this.attributes.aNormal.data[i+1] == -1) && 51 | (this.attributes.aNormal.data[i] < 0) 52 | ) { 53 | offset = 0.5 54 | } 55 | const u = 0.5 + (Math.atan2(this.attributes.aNormal.data[i], this.attributes.aNormal.data[i+2]) / (Math.PI * 2)) 56 | const v = 0.5 - (Math.asin(this.attributes.aNormal.data[i+1]) / Math.PI) 57 | uvs.push(u + offset, 1 - v) 58 | } 59 | 60 | const pointsPerRow = 6 * 2 * segments 61 | 62 | const quarterCircle = 3 * (pointsPerRow / 4) 63 | 64 | for (let i = 0; i < uvs.length; i+=pointsPerRow) { 65 | if (i !== 0) { 66 | uvs[i-(quarterCircle)] = 1 67 | uvs[i-(quarterCircle-2)] = 1 68 | uvs[i-(quarterCircle-6)] = 1 69 | } 70 | } 71 | 72 | uvs[uvs.length - (quarterCircle)] = 1 73 | uvs[uvs.length - (quarterCircle - 2)] = 1 74 | uvs[uvs.length - (quarterCircle - 6)] = 1 75 | 76 | this.setAttribute('aUV', new Float32Array(uvs), 2) 77 | } 78 | } -------------------------------------------------------------------------------- /src/js/modules/Cylinder.js: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry' 2 | 3 | export default class Cylinder extends Geometry { 4 | constructor(radius, height, segments) { 5 | const positions = [] 6 | 7 | 8 | //Front 9 | for (let i = 0; i < segments; i++) { 10 | positions.push(0, 0, height / 2) 11 | 12 | const x1 = Math.cos(i * Math.PI / (segments / 2)) * radius 13 | const y1 = Math.sin(i * Math.PI / (segments / 2)) * radius 14 | const z1 = height / 2 15 | positions.push(x1, y1, z1) 16 | 17 | const x2 = Math.cos((i + 1) * Math.PI / (segments / 2)) * radius 18 | const y2 = Math.sin((i + 1) * Math.PI / (segments / 2)) * radius 19 | const z2 = height / 2 20 | positions.push(x2, y2, z2) 21 | } 22 | 23 | //Back 24 | for (let i = 0; i < segments; i++) { 25 | positions.push(0, 0, -height / 2) 26 | 27 | const x1 = Math.cos(i * Math.PI / (segments / 2)) * radius 28 | const y1 = Math.sin(i * Math.PI / (segments / 2)) * radius 29 | const z1 = -height / 2 30 | positions.push(x1, y1, z1) 31 | 32 | const x2 = Math.cos((i + 1) * Math.PI / (segments / 2)) * radius 33 | const y2 = Math.sin((i + 1) * Math.PI / (segments / 2)) * radius 34 | const z2 = -height / 2 35 | positions.push(x2, y2, z2) 36 | } 37 | 38 | //Sides 39 | for (let i = 0; i < segments; i++) { 40 | const x1 = Math.cos(i * Math.PI / (segments / 2)) * radius 41 | const y1 = Math.sin(i * Math.PI / (segments / 2)) * radius 42 | const z1 = height / 2 43 | positions.push(x1, y1, z1) 44 | 45 | const x2 = Math.cos((i + 1) * Math.PI / (segments / 2)) * radius 46 | const y2 = Math.sin((i + 1) * Math.PI / (segments / 2)) * radius 47 | positions.push(x2, y2, z1) 48 | 49 | const z2 = -height / 2 50 | positions.push(x1, y1, z2) 51 | 52 | positions.push(x2, y2, z2) 53 | positions.push(x1, y1, z2) 54 | positions.push(x2, y2, z1) 55 | } 56 | 57 | //positions.push(Math.cos(0) * radius, Math.sin(0) * radius, 0) 58 | 59 | super(positions) 60 | 61 | const normals = [] 62 | for (var i = 0; i < positions.length; i+=3) { 63 | const x = positions[i] 64 | const y = positions[i + 1] 65 | const z = positions[i + 2] > 0 ? 1 : -1 66 | const magnitude = Math.sqrt((x**2) + (y**2) + (z**2)) 67 | normals.push((x/magnitude), (y/magnitude), (z/magnitude)) 68 | } 69 | this.attributes.aNormal.data = new Float32Array(normals) 70 | this.attributes.aNormal.count = normals.length / 3 71 | 72 | const uvs = [] 73 | for (let i = 0; i < positions.length; i+=3) { 74 | const x = (positions[i] + radius) / (radius * 2) 75 | const y = (positions[i + 1] + radius) / (radius * 2) 76 | uvs.push(x, y) 77 | } 78 | this.setAttribute('aUV', new Float32Array(uvs), 2) 79 | 80 | //this.type = 'TRIANGLE_FAN' 81 | } 82 | } -------------------------------------------------------------------------------- /src/js/modules/Orthographic.js: -------------------------------------------------------------------------------- 1 | import Matrix from './Matrix.js' 2 | 3 | export default class Orthographic { 4 | constructor(left, right, bottom, top, near, far) { 5 | this.left = left 6 | this.right = right 7 | this.bottom = bottom 8 | this.top = top 9 | this.near = near 10 | this.far = far 11 | this.type = 'orthographic' 12 | this.position = { 13 | x: 0, 14 | y: 0, 15 | z: 0 16 | } 17 | this.rotation = { 18 | x: 0, 19 | y: 0, 20 | z: 0 21 | } 22 | this.viewMatrix = Matrix.identity() 23 | this.createMatrix() 24 | } 25 | 26 | createMatrix() { 27 | this.matrix = [ 28 | 2 / (this.right - this.left), 0, 0, 0, 29 | 0, 2 / (this.top - this.bottom), 0, 0, 30 | 0, 0, -2 / (this.far - this.near), 0, 31 | -(this.right + this.left) / (this.right - this.left), -(this.top + this.bottom) / (this.top - this.bottom), -(this.far + this.near) / (this.far - this.near), 1 32 | ] 33 | } 34 | 35 | _recalculateViewMatrix() { 36 | const identity = Matrix.identity() 37 | const translation = Matrix.translate(this.position.x, this.position.y, this.position.z) 38 | const rotationX = Matrix.rotateX(this.rotation.x) 39 | const rotationY = Matrix.rotateY(this.rotation.y) 40 | const rotationZ = Matrix.rotateZ(this.rotation.z) 41 | let matrix = Matrix.multiply(identity, translation) 42 | matrix = Matrix.multiply(matrix, rotationX) 43 | matrix = Matrix.multiply(matrix, rotationY) 44 | matrix = Matrix.multiply(matrix, rotationZ) 45 | if (this.lookAtEnabled) { 46 | matrix = Matrix.lookAt( 47 | [matrix[12], matrix[13], matrix[14]], 48 | [this.lookAtTarget.localMatrix[12], this.lookAtTarget.localMatrix[13], this.lookAtTarget.localMatrix[14]] 49 | ) 50 | } 51 | this.viewMatrix = Matrix.inverse(matrix) 52 | } 53 | 54 | setViewProjectionMatrix() { 55 | this._recalculateViewMatrix() 56 | this.viewProjectionMatrix = Matrix.multiply(this.matrix, this.viewMatrix) 57 | } 58 | 59 | setLeft(left) { 60 | this.left = left 61 | this.createMatrix() 62 | } 63 | 64 | setRight(right) { 65 | this.right = right 66 | this.createMatrix() 67 | } 68 | 69 | setBottom(bottom) { 70 | this.bottom = bottom 71 | this.createMatrix() 72 | } 73 | 74 | setTop(top) { 75 | this.top = top 76 | this.createMatrix() 77 | } 78 | 79 | setNear(near) { 80 | this.near = near 81 | this.createMatrix() 82 | } 83 | 84 | setFar(far) { 85 | this.far = far 86 | this.createMatrix() 87 | } 88 | 89 | setPosition(x, y, z) { 90 | this.position = { x, y, z } 91 | } 92 | 93 | setRotationX(angle) { 94 | this.rotation.x = angle 95 | } 96 | 97 | setRotationY(angle) { 98 | this.rotation.y = angle 99 | } 100 | 101 | setRotationZ(angle) { 102 | this.rotation.z = angle 103 | } 104 | 105 | lookAt(target) { 106 | this.lookAtEnabled = true 107 | this.lookAtTarget = target 108 | } 109 | } -------------------------------------------------------------------------------- /src/js/modules/Light.js: -------------------------------------------------------------------------------- 1 | import Vector from './Vector.js' 2 | 3 | export default class Light { 4 | constructor(type, vector) { 5 | this.type = type 6 | if (this.type === 'directional') { 7 | this.direction = Vector.normalize([vector[0], vector[1], vector[2]]) 8 | } else if (this.type === 'point') { 9 | this.position = [vector[0], vector[1], vector[2]] 10 | } 11 | } 12 | 13 | /* 14 | VERTEX SHADER REQUIREMENTS 15 | Using a directional light requires calculating the normals relative to rotation and scale 16 | to pass to the fragment shader: 17 | 18 | vNormal = mat3(uNormalMatrix) * aNormal; 19 | 20 | FRAGMENT SHADER REQUIREMENTS 21 | Normals must be normalized: 22 | 23 | vec3 normal = normalize(vNormal); 24 | 25 | The dot product of the directional light vector and the normals will determine the lighting: 26 | 27 | float light = dot(normal, uDirectionalLight); 28 | 29 | Light is computed by multiplying the existing fragment color by the light value: 30 | 31 | gl_FragColor.rgb += gl_FragColor.rgb * ((light + 1.0) * 0.5); 32 | 33 | */ 34 | setDirection(x, y, z) { 35 | this.direction = Vector.normalize([x, y, z]) 36 | } 37 | 38 | /* 39 | VERTEX SHADER REQUIREMENTS 40 | Using a point light requires calculating the surface position as follows: 41 | 42 | vec3 surfacePosition = (uLocalMatrix * aPosition).xyz; 43 | 44 | Then one must calculate the normals relative to rotation and scale to pass to the fragment shader: 45 | 46 | vNormal = mat3(uNormalMatrix) * aNormal; 47 | 48 | Lastly, vectors representing the surface position to the light position and the surface position 49 | to the camera position should be passed to the fragment shader: 50 | 51 | vSurfaceToLight = uPointLight - surfacePosition; 52 | vSurfaceToCamera = uCameraPosition - surfacePosition; 53 | 54 | FRAGMENT SHADER REQUIREMENTS 55 | Normals, surface to light, and surface to camera vectors must be normalized: 56 | 57 | vec3 normal = normalize(vNormal); 58 | vec3 surfaceToLight = normalize(vSurfaceToLight); 59 | vec3 surfaceToCamera = normalize(vSurfaceToCamera); 60 | 61 | The half vector must be computed in order to get specular lighting: 62 | 63 | vec3 halfVector = normalize(vSurfaceToLight + vSurfaceToCamera); 64 | 65 | The dot product of the surface to light vector and the normals will determine the lighting: 66 | 67 | float light = dot(normal, surfaceToLight); 68 | 69 | The specular highlight requires the dot product of the normals and half vector raised to some power 70 | if the light value is greater than zero: 71 | 72 | float specular = 0.0; 73 | if (light > 0.0) { 74 | specular = pow(dot(normal, halfVector), 16.0); 75 | } 76 | 77 | Light is computed by multiplying the existing fragment color by the light value, while the specular 78 | is added to the final color value: 79 | 80 | gl_FragColor.rgb += gl_FragColor.rgb * ((light + 1.0) * 0.5); 81 | gl_FragColor.rgb += specular; 82 | 83 | */ 84 | setPosition(x, y, z) { 85 | this.position = [x, y, z] 86 | } 87 | } -------------------------------------------------------------------------------- /src/js/modules/Perspective.js: -------------------------------------------------------------------------------- 1 | import Matrix from './Matrix.js' 2 | 3 | export default class Perspective { 4 | constructor(fieldOfView, aspectRatio, near, far) { 5 | this.fieldOfView = fieldOfView * Math.PI / 180 6 | this.aspectRatio = aspectRatio 7 | this.near = near 8 | this.far = far 9 | this.type = 'perspective' 10 | this.position = { 11 | x: 0, 12 | y: 0, 13 | z: 0 14 | } 15 | this.rotation = { 16 | x: 0, 17 | y: 0, 18 | z: 0 19 | } 20 | this.viewMatrix = Matrix.identity() 21 | this.lookAtEnabled = false 22 | this.createMatrix() 23 | } 24 | 25 | createMatrix() { 26 | this.top = this.near * Math.tan(this.fieldOfView / 2) 27 | this.bottom = -this.top 28 | this.right = this.top * this.aspectRatio 29 | this.left = -this.right 30 | 31 | this.matrix = [ 32 | 2 * this.near / (this.right - this.left), 0, 0, 0, 33 | 0, 2 * this.near / (this.top - this.bottom), 0, 0, 34 | 0, 0, -(this.far + this.near) / (this.far - this.near), -1, 35 | -this.near * (this.right + this.left) / (this.right - this.left), -this.near * (this.top + this.bottom) / (this.top - this.bottom), (2 * this.far * this.near) / (this.near - this.far), 0 36 | ] 37 | } 38 | 39 | _recalculateViewMatrix() { 40 | const identity = Matrix.identity() 41 | const translation = Matrix.translate(this.position.x, this.position.y, this.position.z) 42 | const rotationX = Matrix.rotateX(this.rotation.x) 43 | const rotationY = Matrix.rotateY(this.rotation.y) 44 | const rotationZ = Matrix.rotateZ(this.rotation.z) 45 | let matrix = Matrix.multiply(identity, translation) 46 | matrix = Matrix.multiply(matrix, rotationX) 47 | matrix = Matrix.multiply(matrix, rotationY) 48 | matrix = Matrix.multiply(matrix, rotationZ) 49 | if (this.lookAtEnabled) { 50 | matrix = Matrix.lookAt( 51 | [matrix[12], matrix[13], matrix[14]], 52 | [this.lookAtTarget.localMatrix[12], this.lookAtTarget.localMatrix[13], this.lookAtTarget.localMatrix[14]] 53 | ) 54 | } 55 | this.viewMatrix = Matrix.inverse(matrix) 56 | } 57 | 58 | setViewProjectionMatrix() { 59 | this._recalculateViewMatrix() 60 | this.viewProjectionMatrix = Matrix.multiply(this.matrix, this.viewMatrix) 61 | } 62 | 63 | setFieldOfView(fieldOfView) { 64 | this.fieldOfView = fieldOfView * Math.PI / 180 65 | this.createMatrix() 66 | } 67 | 68 | setAspectRatio(aspectRatio) { 69 | this.aspectRatio = aspectRatio 70 | this.createMatrix() 71 | } 72 | 73 | setNear(near) { 74 | this.near = near 75 | this.createMatrix() 76 | } 77 | 78 | setFar(far) { 79 | this.far = far 80 | this.createMatrix() 81 | } 82 | 83 | setPosition(x, y, z) { 84 | this.position = { x, y, z } 85 | } 86 | 87 | setRotationX(angle) { 88 | this.rotation.x = angle 89 | } 90 | 91 | setRotationY(angle) { 92 | this.rotation.y = angle 93 | } 94 | 95 | setRotationZ(angle) { 96 | this.rotation.z = angle 97 | } 98 | 99 | lookAt(target) { 100 | this.lookAtEnabled = true 101 | this.lookAtTarget = target 102 | } 103 | } -------------------------------------------------------------------------------- /src/js/modules/ColorPicker.js: -------------------------------------------------------------------------------- 1 | export default class ColorPicker { 2 | constructor(gl, mouse, camera) { 3 | this.gl = gl 4 | this.mouse = mouse 5 | this.camera = camera 6 | this.color = new Uint8Array(4) 7 | this.selectedIndex = -1 8 | this.objectCount = 0 9 | this.viewport = { 10 | width: window.innerWidth, 11 | height: window.innerHeight, 12 | aspectRatio: window.innerWidth / window.innerHeight 13 | } 14 | } 15 | 16 | resize(viewport) { 17 | this.viewport.width = viewport.width 18 | this.viewport.height = viewport.height 19 | this.viewport.aspectRatio = viewport.width / viewport.height 20 | } 21 | 22 | _getPixel() { 23 | this.pixel = { 24 | x: this.mouse.x * this.viewport.width / this.viewport.width, 25 | y: this.viewport.height - this.mouse.y * this.viewport.height / this.viewport.height - 1 26 | } 27 | } 28 | 29 | _getColor() { 30 | this.gl.readPixels(0, 0, 1, 1, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.color) 31 | } 32 | 33 | getMatrix() { 34 | this._getPixel() 35 | if (this.camera.type === 'perspective') { 36 | this.top = this.camera.near * Math.tan(this.camera.fieldOfView / 2) 37 | this.bottom = -this.top 38 | this.right = this.top * this.camera.aspectRatio 39 | this.left = -this.right 40 | } else if (this.camera.type === 'orthographic') { 41 | this.top = this.camera.top 42 | this.bottom = this.camera.bottom 43 | this.right = this.camera.right 44 | this.left = this.camera.left 45 | } 46 | this.width = Math.abs(this.right - this.left) 47 | this.height = Math.abs(this.top - this.bottom) 48 | 49 | this.pixelLeft = this.left + this.pixel.x * this.width / this.viewport.width 50 | this.pixelRight = this.pixelLeft + (1 / this.viewport.width) 51 | this.pixelTop = this.bottom + this.pixel.y * this.height / this.viewport.height 52 | this.pixelBottom = this.pixelTop + (1 / this.viewport.height) 53 | this.near = this.camera.near 54 | this.far = this.camera.far 55 | 56 | if (this.camera.type === 'perspective') { 57 | this.matrix = [ 58 | 2 * this.near / (this.pixelRight - this.pixelLeft), 0, 0, 0, 59 | 0, 2 * this.near / (this.pixelTop - this.pixelBottom), 0, 0, 60 | (this.pixelRight + this.pixelLeft) / (this.pixelRight - this.pixelLeft), (this.pixelTop + this.pixelBottom) / (this.pixelTop - this.pixelBottom), -(this.far + this.near) / (this.far - this.near), -1, 61 | 0, 0, (2 * this.far * this.near) / (this.near - this.far), 0 62 | ] 63 | } else if (this.camera.type === 'orthographic') { 64 | this.matrix = [ 65 | 2 / (this.pixelRight - this.pixelLeft), 0, 0, 0, 66 | 0, 2 / (this.pixelTop - this.pixelBottom), 0, 0, 67 | 0, 0, -2 / (this.far - this.near), 0, 68 | -((this.pixelRight + this.pixelLeft) / (this.pixelRight - this.pixelLeft)), -((this.pixelTop + this.pixelBottom) / (this.pixelTop - this.pixelBottom)), -((this.far + this.near) / (this.far - this.near)), 1 69 | ] 70 | } 71 | 72 | return this.matrix 73 | } 74 | 75 | getObjectIndex() { 76 | this._getColor() 77 | this.selectedIndex = ((this.color[3] / 255) * this.objectCount) - 1 78 | return this.selectedIndex 79 | } 80 | } -------------------------------------------------------------------------------- /src/js/modules/Cube.js: -------------------------------------------------------------------------------- 1 | import Geometry from './Geometry' 2 | 3 | export default class Cube extends Geometry { 4 | constructor(width, height, depth, widthSegments, heightSegments, depthSegments) { 5 | const positions = [] 6 | const uvs = [] 7 | 8 | //Front 9 | createSide('x', 'y', 'z', width, height, depth, widthSegments, heightSegments, 'front', false, false) 10 | 11 | //Back 12 | createSide('x', 'y', 'z', width, height, -depth, widthSegments, heightSegments, 'back', true, false) 13 | 14 | //Top 15 | createSide('x', 'z', 'y', width, depth, height, widthSegments, depthSegments, 'back', false, true) 16 | 17 | //Bottom 18 | createSide('x', 'z', 'y', width, depth, -height, widthSegments, depthSegments, 'front', false, false) 19 | 20 | //Right 21 | createSide('z', 'y', 'x', depth, height, width, depthSegments, heightSegments, 'back', true, false) 22 | 23 | //Left 24 | createSide('z', 'y', 'x', depth, height, -width, depthSegments, heightSegments, 'front', false, false) 25 | 26 | function createSide(x, y, z, xLength, yLength, depth, xSegments, ySegments, direction, uvFlipX, uvFlipY) { 27 | const segmentX = xLength / xSegments 28 | const segmentY = yLength / ySegments 29 | 30 | const z1 = depth / 2 31 | 32 | for (let i = 0; i < ySegments; i++) { 33 | for (let j = 0; j < xSegments; j++) { 34 | const point = {} 35 | point[x] = [] 36 | point[y] = [] 37 | point[z] = [] 38 | 39 | const x1 = (j * segmentX) - xLength / 2 40 | const y1 = (i * segmentY) - yLength / 2 41 | const x2 = ((j + 1) * segmentX) - xLength / 2 42 | const y2 = ((i + 1) * segmentY) - yLength / 2 43 | if (direction === 'front') { 44 | point[x].push(x1) 45 | point[y].push(y1) 46 | point[z].push(z1) 47 | 48 | point[x].push(x2) 49 | point[y].push(y1) 50 | point[z].push(z1) 51 | 52 | point[x].push(x1) 53 | point[y].push(y2) 54 | point[z].push(z1) 55 | 56 | point[x].push(x1) 57 | point[y].push(y2) 58 | point[z].push(z1) 59 | 60 | point[x].push(x2) 61 | point[y].push(y1) 62 | point[z].push(z1) 63 | 64 | point[x].push(x2) 65 | point[y].push(y2) 66 | point[z].push(z1) 67 | } else if (direction === 'back') { 68 | point[x].push(x2) 69 | point[y].push(y1) 70 | point[z].push(z1) 71 | 72 | point[x].push(x1) 73 | point[y].push(y1) 74 | point[z].push(z1) 75 | 76 | point[x].push(x2) 77 | point[y].push(y2) 78 | point[z].push(z1) 79 | 80 | point[x].push(x2) 81 | point[y].push(y2) 82 | point[z].push(z1) 83 | 84 | point[x].push(x1) 85 | point[y].push(y1) 86 | point[z].push(z1) 87 | 88 | point[x].push(x1) 89 | point[y].push(y2) 90 | point[z].push(z1) 91 | } 92 | 93 | positions.push( 94 | point.x[0], point.y[0], point.z[0], 95 | point.x[1], point.y[1], point.z[1], 96 | point.x[2], point.y[2], point.z[2], 97 | point.x[3], point.y[3], point.z[3], 98 | point.x[4], point.y[4], point.z[4], 99 | point.x[5], point.y[5], point.z[5] 100 | ) 101 | 102 | for (let k = 0; k < 6; k++) { 103 | let uvX 104 | let uvY 105 | if (uvFlipX) { 106 | uvX = 1 - (point[x][k] + xLength/2) / xLength 107 | } else { 108 | uvX = (point[x][k] + xLength/2) / xLength 109 | } 110 | if (uvFlipY) { 111 | uvY = 1 - (point[y][k] + yLength/2) / yLength 112 | } else { 113 | uvY = (point[y][k] + yLength/2) / yLength 114 | } 115 | uvs.push(uvX, uvY) 116 | } 117 | } 118 | } 119 | 120 | 121 | } 122 | 123 | super(positions) 124 | this.setAttribute('aUV', new Float32Array(uvs), 2) 125 | } 126 | } -------------------------------------------------------------------------------- /src/js/modules/Renderer.js: -------------------------------------------------------------------------------- 1 | export default class Renderer { 2 | constructor(element) { 3 | this.gl = element.getContext('webgl', { 4 | powerPreference: 'high-performance' 5 | }) 6 | 7 | this.resize = this.resize.bind(this) 8 | this.render = this.render.bind(this) 9 | this.depthTest = true 10 | this.faceCulling = true 11 | this.pixelRatio = Math.min(window.devicePixelRatio, 2.0) 12 | this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true) 13 | this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, 1) 14 | this.framebuffer = null 15 | } 16 | 17 | setPixelRatio(ratio) { 18 | this.pixelRatio = ratio 19 | } 20 | 21 | setFrameBuffer(framebuffer) { 22 | if (framebuffer !== null) { 23 | this.framebuffer = framebuffer.buffer 24 | } else { 25 | this.framebuffer = null 26 | } 27 | } 28 | 29 | resize() { 30 | const displayWidth = this.gl.canvas.clientWidth * this.pixelRatio 31 | const displayHeight = this.gl.canvas.clientHeight * this.pixelRatio 32 | 33 | const needsResize = this.gl.canvas.width * this.pixelRatio !== displayWidth || this.gl.canvas.height * this.pixelRatio !== displayHeight 34 | 35 | if (needsResize) { 36 | this.gl.canvas.width = displayWidth 37 | this.gl.canvas.height = displayHeight 38 | 39 | this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height) 40 | 41 | return true 42 | } 43 | 44 | return false 45 | } 46 | 47 | render(volume, camera) { 48 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer) 49 | this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT) 50 | 51 | if (this.faceCulling) { 52 | this.gl.enable(this.gl.CULL_FACE) 53 | } 54 | if (this.depthTest) { 55 | this.gl.enable(this.gl.DEPTH_TEST) 56 | } 57 | this.gl.enable(this.gl.BLEND) 58 | this.gl.blendEquation( this.gl.FUNC_ADD ) 59 | this.gl.blendFunc( this.gl.ONE_MINUS_CONSTANT_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA ) 60 | 61 | let lastShader = null 62 | let lastBuffer = null 63 | 64 | camera.setViewProjectionMatrix() 65 | 66 | for (const object of volume.objects) { 67 | object.setProjectionMatrix(camera.viewProjectionMatrix) 68 | let bindBuffers = false 69 | 70 | if (object.shader.program !== lastShader) { 71 | this.gl.useProgram(object.shader.program) 72 | lastShader = object.shader.program 73 | bindBuffers = true 74 | } 75 | 76 | if (bindBuffers || object.attributes != lastBuffer) { 77 | for (const attribute in object.attributes) { 78 | this.gl.enableVertexAttribArray(object.attributes[attribute].location) 79 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, object.attributes[attribute].buffer) 80 | const size = object.attributes[attribute].size 81 | const type = this.gl.FLOAT 82 | const normalize = false 83 | const stride = 0 84 | const offset = 0 85 | this.gl.vertexAttribPointer(object.attributes[attribute].location, size, type, normalize, stride, offset) 86 | } 87 | lastBuffer = object.attributes 88 | } 89 | 90 | for (const uniform in object.uniforms) { 91 | if (uniform === 'uViewProjectionMatrix') { 92 | this.gl.uniformMatrix4fv(object.uniforms[uniform].location, false, object.projectionMatrix) 93 | } else if (uniform === 'uNormalMatrix') { 94 | this.gl.uniformMatrix4fv(object.uniforms[uniform].location, false, object.normalMatrix) 95 | } else if (uniform === 'uLocalMatrix') { 96 | this.gl.uniformMatrix4fv(object.uniforms[uniform].location, false, object.localMatrix) 97 | } else { 98 | switch (object.uniforms[uniform].type) { 99 | case '1f': 100 | this.gl.uniform1f(object.uniforms[uniform].location, object.uniforms[uniform].value) 101 | break 102 | case '2f': 103 | this.gl.uniform2f(object.uniforms[uniform].location, object.uniforms[uniform].value[0], object.uniforms[uniform].value[1]) 104 | break 105 | case '3f': 106 | this.gl.uniform3f(object.uniforms[uniform].location, object.uniforms[uniform].value[0], object.uniforms[uniform].value[1], object.uniforms[uniform].value[2]) 107 | break 108 | case '4f': 109 | this.gl.uniform4f(object.uniforms[uniform].location, object.uniforms[uniform].value[0], object.uniforms[uniform].value[1], object.uniforms[uniform].value[2], object.uniforms[uniform].value[3]) 110 | break 111 | case 'mat3': 112 | this.gl.uniformMatrix3fv(object.uniforms[uniform].location, false, object.uniforms[uniform].value) 113 | break 114 | case 'mat4': 115 | this.gl.uniformMatrix4fv(object.uniforms[uniform].location, false, object.uniforms[uniform].value) 116 | break 117 | case 'tex': 118 | this.gl.uniform1i(object.uniforms[uniform].location, object.uniforms[uniform].value.id) 119 | this.gl.activeTexture(this.gl.TEXTURE0 + object.uniforms[uniform].value.id) 120 | this.gl.bindTexture(this.gl.TEXTURE_2D, object.uniforms[uniform].value.texture) 121 | default: 122 | break 123 | } 124 | } 125 | } 126 | 127 | const primitiveType = this.gl[object.drawMode] 128 | const vertexOffset = 0 129 | const count = object.attributes.aPosition.count 130 | this.gl.drawArrays(primitiveType, vertexOffset, count) 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /src/js/modules/Mesh.js: -------------------------------------------------------------------------------- 1 | import Matrix from './Matrix.js' 2 | import Vector from './Vector.js' 3 | 4 | export default class Mesh { 5 | constructor(geometry, shader) { 6 | this.geometryID = geometry.id 7 | this.geometryType = geometry.type 8 | this.shader = shader 9 | this.position = { 10 | x: 0, 11 | y: 0, 12 | z: 0 13 | } 14 | this.rotation = { 15 | x: 0, 16 | y: 0, 17 | z: 0 18 | } 19 | this.scale = { 20 | x: 1, 21 | y: 1, 22 | z: 1 23 | } 24 | this.attributes = geometry.attributes 25 | this.uniforms = { 26 | uViewProjectionMatrix: { 27 | name: 'uViewProjectionMatrix', 28 | value: null, 29 | type: 'mat4' 30 | }, 31 | uNormalMatrix: { 32 | name: 'uNormalMatrix', 33 | value: null, 34 | type: 'mat4' 35 | }, 36 | uLocalMatrix: { 37 | name: 'uLocalMatrix', 38 | value: null, 39 | type: 'mat4' 40 | } 41 | } 42 | this.surfaceNormals = false 43 | this.localMatrix = Matrix.identity() 44 | this._setAttributeData() 45 | this._setUniformData() 46 | this._setDrawMode() 47 | this._setSurfaceNormals() 48 | } 49 | 50 | _setAttributeData() { 51 | for (const attribute in this.attributes) { 52 | this.attributes[attribute].location = this.shader.gl.getAttribLocation(this.shader.program, this.attributes[attribute].name) 53 | this.attributes[attribute].buffer = this.shader.gl.createBuffer() 54 | this.shader.gl.bindBuffer(this.shader.gl.ARRAY_BUFFER, this.attributes[attribute].buffer) 55 | this.shader.gl.bufferData(this.shader.gl.ARRAY_BUFFER, this.attributes[attribute].data, this.shader.gl.STATIC_DRAW) 56 | } 57 | } 58 | 59 | _setUniformData() { 60 | for (const uniform in this.uniforms) { 61 | this.uniforms[uniform].location = this.shader.gl.getUniformLocation(this.shader.program, this.uniforms[uniform].name) 62 | } 63 | } 64 | 65 | _setDrawMode() { 66 | if (this.geometryType) { 67 | this.drawMode = this.geometryType 68 | } else { 69 | this.drawMode = 'TRIANGLES' 70 | } 71 | } 72 | 73 | _setSurfaceNormals() { 74 | if (this.surfaceNormals) { 75 | const surfaceNormals = [] 76 | for (let i = 0; i < this.geometry.attributes.aNormal.data.length; i+=9) { 77 | const p1 = [this.geometry.attributes.aNormal.data[i], this.geometry.attributes.aNormal.data[i+1], this.geometry.attributes.aNormal.data[i+2]] 78 | const p2 = [this.geometry.attributes.aNormal.data[i+3], this.geometry.attributes.aNormal.data[i+4], this.geometry.attributes.aNormal.data[i+5]] 79 | const p3 = [this.geometry.attributes.aNormal.data[i+6], this.geometry.attributes.aNormal.data[i+7], this.geometry.attributes.aNormal.data[i+8]] 80 | 81 | const u = Vector.subtract(p2, p1) 82 | const v = Vector.subtract(p3, p1) 83 | 84 | const x = (u[1] * v[2]) - (u[2] * v[1]) 85 | const y = (u[2] * v[0]) - (u[0] * v[2]) 86 | const z = (u[0] * v[1]) - (u[1] * v[0]) 87 | 88 | const normals = Vector.normalize([x, y, z]) 89 | 90 | surfaceNormals.push(normals[0], normals[1], normals[2]) 91 | surfaceNormals.push(normals[0], normals[1], normals[2]) 92 | surfaceNormals.push(normals[0], normals[1], normals[2]) 93 | } 94 | this.shader.gl.bindBuffer(this.shader.gl.ARRAY_BUFFER, this.geometry.attributes.aNormal.buffer) 95 | this.shader.gl.bufferData(this.shader.gl.ARRAY_BUFFER, new Float32Array(surfaceNormals), this.shader.gl.STATIC_DRAW) 96 | } 97 | } 98 | 99 | _recalculateModelMatrix() { 100 | const identity = Matrix.identity() 101 | const translation = Matrix.translate(this.position.x, this.position.y, this.position.z) 102 | const rotationX = Matrix.rotateX(this.rotation.x) 103 | const rotationY = Matrix.rotateY(this.rotation.y) 104 | const rotationZ = Matrix.rotateZ(this.rotation.z) 105 | const scale = Matrix.scale(this.scale.x, this.scale.y, this.scale.z) 106 | let matrix = Matrix.multiply(identity, translation) 107 | matrix = Matrix.multiply(matrix, rotationX) 108 | matrix = Matrix.multiply(matrix, rotationY) 109 | matrix = Matrix.multiply(matrix, rotationZ) 110 | matrix = Matrix.multiply(matrix, scale) 111 | if (this.parentCollection) { 112 | this.localMatrix = Matrix.multiply(this.parentCollection.localMatrix, matrix) 113 | } else { 114 | this.localMatrix = matrix 115 | } 116 | } 117 | 118 | _recalculateNormalMatrix() { 119 | this.normalMatrix = Matrix.transpose(Matrix.inverse(this.localMatrix)) 120 | } 121 | 122 | setProjectionMatrix(matrix) { 123 | this._recalculateModelMatrix() 124 | this._recalculateNormalMatrix() 125 | this.projectionMatrix = matrix 126 | } 127 | 128 | setPosition(x, y, z) { 129 | this.position = { x, y, z } 130 | this._recalculateModelMatrix() 131 | } 132 | 133 | setRotationX(angle) { 134 | this.rotation.x = angle 135 | this._recalculateModelMatrix() 136 | } 137 | 138 | setRotationY(angle) { 139 | this.rotation.y = angle 140 | this._recalculateModelMatrix() 141 | } 142 | 143 | setRotationZ(angle) { 144 | this.rotation.z = angle 145 | this._recalculateModelMatrix() 146 | } 147 | 148 | setScale(x, y, z) { 149 | this.scale = { x, y, z } 150 | this._recalculateModelMatrix() 151 | } 152 | 153 | setAttribute(name, data, size) { 154 | this.attributes[name] = { 155 | name, 156 | data, 157 | size, 158 | count: data.length / size 159 | } 160 | this._setAttributeData() 161 | } 162 | 163 | setUniform(name, value, type) { 164 | this.uniforms[name] = { 165 | name, 166 | value, 167 | type 168 | } 169 | this._setUniformData() 170 | } 171 | 172 | setShader(shader) { 173 | this.shader = shader 174 | this._setAttributeData() 175 | this._setUniformData() 176 | } 177 | 178 | setParent(collection) { 179 | if (this.parentCollection) { 180 | let index = this.parentCollection.items.indexOf(this) 181 | if (index >= 0) { 182 | this.parentCollection.items.splice(index, 1) 183 | } 184 | } 185 | 186 | if (collection) { 187 | collection.items.push(this) 188 | } 189 | this.parentCollection = collection 190 | } 191 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Sandbox 4 | A simple WebGL library for creating 3D scenes. 5 | 6 | ## Demo 7 | Open the index.html file to view a demo of the essential features. 8 | 9 | ## Getting Started 10 | To create a 3D scene in Sandbox you will need a few things: 11 | 1. A canvas element 12 | 2. A Renderer object 13 | 3. A Camera object 14 | 4. At least one Mesh consisting of both geometry data and a shader program 15 | 5. A Volume to contain meshes 16 | 17 | ### Creating a Renderer object 18 | A Renderer's constructor requires the canvas element as an argument to be the render target. The renderer has a 'resize' method that can be called whenever the canvas dimensions have changed. The 'pixelRatio' attribute can be set to account for high-density displays; by default the pixel ratio is set to either the display's pixel ratio or a value of 2 depending on which is smaller. The 'depthTest' and 'faceCulling' attributes are exposed from the OpenGL implementation for additional customization. 19 | ```javascript 20 | const canvas = document.getElementById('webgl-canvas') 21 | const renderer = new Sandbox.Renderer(canvas) 22 | 23 | //Methods and attributes 24 | renderer.resize() 25 | renderer.pixelRatio = 2.0 26 | renderer.depthTest = true 27 | renderer.faceCulling = true 28 | ``` 29 | 30 | ### Creating a Camera object 31 | Sandbox includes two types of Camera objects, a Perspective Camera and an Orthographic Camera. Common to both Camera types are a few methods and attributes: 32 | - A 'position' object containing 'x', 'y', and 'z' attributes 33 | - A 'rotation' object containing 'x', 'y', and 'z' attributes 34 | - A 'lookAtEnabled' flag to determine if the current 'lookAt' target should be used 35 | - A 'createMatrix' method that regenerates the Camera's local matrix based on current viewing frustum attributes 36 | - A 'setViewProjectionMatrix' method that regenerates the viewProjection matrix based on the current local matrix 37 | - A 'setNear' method used to set the near plane of the viewing frustum 38 | - A 'setFar' method used to set the far plane of the viewing frustum 39 | - A 'setPosition' method that takes 'x', 'y', and 'z' arguments 40 | - A 'setRotationX' method to set the Camera's X rotation 41 | - A 'setRotationY' method to set the Camera's Y rotation 42 | - A 'setRotationZ' method to set the Camera's Z rotation 43 | - A 'lookAt' method which takes a Mesh target to force the Camera to center the target in its field of view regardless of position 44 | 45 | #### The Perspective Camera 46 | A Perspective Camera requires four arguments: 47 | 1. A 'fieldOfView' 48 | 2. An 'aspectRatio' 49 | 3. A 'near' plane location 50 | 4. A 'far' plane location 51 | ```javascript 52 | const camera = new Sandbox.Perspective(35, 1.85, 0.1, 100) 53 | ``` 54 | 55 | #### The Orthographic Camera 56 | A Perspective Camera requires six arguments: 57 | 1. A 'left' plane location 58 | 2. A 'right' plane location 59 | 3. A 'bottom' plane location 60 | 4. A 'top' plane location 61 | 5. A 'near' plane location 62 | 6. A 'far' plane location 63 | ```javascript 64 | const camera = new Sandbox.Orthographic(-1, 1, -1, 1, -1, 1) 65 | ``` 66 | 67 | ### Creating a Mesh 68 | A Mesh consists of both geometry data and a shader program. 69 | 70 | #### The base Geometry class 71 | To create a custom Geometry object, pass in an array of position data as 3D positions. 72 | ```javascript 73 | const positions = [ 74 | 1, 0, 0, //x, y, z 75 | 0, 1, 0, //x, y, z 76 | 0, 0, 1 //x, y, z 77 | ] 78 | const geometry = new Sandbox.Geometry(positions) 79 | ``` 80 | 81 | #### The Primitives 82 | While geometry data can be created manually with the Geometry class, various primitives are included as part of Sandbox including: 83 | - Plane 84 | - Circle 85 | - Tetrahedron 86 | - Cube 87 | - Sphere 88 | - Cylinder 89 | 90 | ##### Plane 91 | ```javascript 92 | new Sandbox.Plane(width, height, widthSegments, heightSegments) 93 | ``` 94 | 95 | ##### Circle 96 | ```javascript 97 | new Sandbox.Circle(radius, segments) 98 | ``` 99 | 100 | ##### Tetrahedron 101 | ```javascript 102 | new Sandbox.Tetrahedron(size) 103 | ``` 104 | 105 | ##### Cube 106 | ```javascript 107 | new Sandbox.Cube(width, height, depth, widthSegments, heightSegments, depthSegments) 108 | ``` 109 | 110 | ##### Sphere 111 | ```javascript 112 | new Sandbox.Sphere(radius, segments) 113 | ``` 114 | 115 | ##### Cylinder 116 | ```javascript 117 | new Sandbox.Cylinder(radius, height, segments) 118 | ``` 119 | 120 | #### Shader Programs 121 | A shader program requires that you pass in a reference the Renderer object's 'gl' attribute, a vertex shader string, and a fragment shader string. Sandbox accepts GLSL strings. 122 | ```javascript 123 | const shader = new Sandbox.Program(renderer.gl, vertex, fragment) 124 | ``` 125 | 126 | #### The Mesh Object 127 | ```javascript 128 | const mesh = new Sandbox.Mesh(geometry, shader) 129 | ``` 130 | 131 | The Mesh object has the same 'position' and 'rotation' attributes, as well as the 'setPosition', 'setRotationX', 'setRotationY', and 'setRotationZ' methods that the Camera objects have as well as an additional attribute 'scale' and a 'setScale' method which takes an 'x', 'y', and 'z' value. 132 | 133 | ##### Mesh Attributes 134 | Attributes in addition to the 'aPosition' attributed generated by the geometry data can be added through the 'setAttribute' method. Attributes must exist in the vertex shader in order for attribute data to properly work. The 'setAttribute' method takes three arguments: 135 | 1. A 'name' for the attribute that will be referenced in the vertex shader 136 | 2. A Float32Array of attribute data 137 | 3. A size of the data indicating how many array values exist per vertex. 138 | ```javascript 139 | mesh.setAttribute('aColor', new Float32Array(colorData), 3) 140 | ``` 141 | 142 | ##### Mesh Uniforms 143 | Uniforms can be supplied to Mesh objects through the 'setUniform' method. Uniforms must exist in the vertex shader in order for uniform data to properly work. The 'setUniform' method takes three arguments: 144 | 1. A 'name' for the uniform that will be referenced in the vertex shader 145 | 2. A current value for the uniform. Sandbox supports floats, JavaScript arrays containing 2-4 floats, 3D and 4D matricies, and textures. 146 | 3. A data type for the uniform, either '1f', '2f', '3f', '4f', 'mat3', 'mat4', or 'tex' 147 | ```javascript 148 | mesh.setUniform('uTime', 0, '1f') 149 | ``` 150 | 151 | ##### Swapping Shaders 152 | The shader initially set on creation of the Mesh can be swapped for an alternate using the 'setShader' method. 153 | ```javascript 154 | mesh.setShader(shader) 155 | ``` 156 | 157 | ### Adding a Mesh to a Volume 158 | Create a volume then add a Mesh using the 'add' method. 159 | ```javascript 160 | const volume = new Sandbox.Volume() 161 | volume.add(mesh) 162 | ``` 163 | 164 | ### Rendering the Volume 165 | Establish a clear color for the Renderer and then render the Volume with a Camera 166 | ```javascript 167 | renderer.gl.clearColor(0, 0, 0, 0) 168 | renderer.render(volume, camera) 169 | ``` 170 | 171 | ## Todos 172 | Documentation and demos for Collections, Color Picking, Lights, Texturing, and Framebuffer Rendering are still in progress. 173 | 174 | ## License 175 | Licensed under [the MIT license](LICENSE.md). 176 | 177 | 178 | -------------------------------------------------------------------------------- /src/js/modules/Matrix.js: -------------------------------------------------------------------------------- 1 | import Vector from './Vector.js' 2 | 3 | export default class Matrix { 4 | static multiply(a, b) { 5 | const b00 = b[0 * 4 + 0] 6 | const b01 = b[0 * 4 + 1] 7 | const b02 = b[0 * 4 + 2] 8 | const b03 = b[0 * 4 + 3] 9 | const b10 = b[1 * 4 + 0] 10 | const b11 = b[1 * 4 + 1] 11 | const b12 = b[1 * 4 + 2] 12 | const b13 = b[1 * 4 + 3] 13 | const b20 = b[2 * 4 + 0] 14 | const b21 = b[2 * 4 + 1] 15 | const b22 = b[2 * 4 + 2] 16 | const b23 = b[2 * 4 + 3] 17 | const b30 = b[3 * 4 + 0] 18 | const b31 = b[3 * 4 + 1] 19 | const b32 = b[3 * 4 + 2] 20 | const b33 = b[3 * 4 + 3] 21 | const a00 = a[0 * 4 + 0] 22 | const a01 = a[0 * 4 + 1] 23 | const a02 = a[0 * 4 + 2] 24 | const a03 = a[0 * 4 + 3] 25 | const a10 = a[1 * 4 + 0] 26 | const a11 = a[1 * 4 + 1] 27 | const a12 = a[1 * 4 + 2] 28 | const a13 = a[1 * 4 + 3] 29 | const a20 = a[2 * 4 + 0] 30 | const a21 = a[2 * 4 + 1] 31 | const a22 = a[2 * 4 + 2] 32 | const a23 = a[2 * 4 + 3] 33 | const a30 = a[3 * 4 + 0] 34 | const a31 = a[3 * 4 + 1] 35 | const a32 = a[3 * 4 + 2] 36 | const a33 = a[3 * 4 + 3] 37 | 38 | return [ 39 | b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, 40 | b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, 41 | b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, 42 | b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, 43 | b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, 44 | b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, 45 | b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, 46 | b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, 47 | b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, 48 | b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, 49 | b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, 50 | b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, 51 | b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, 52 | b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, 53 | b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, 54 | b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33, 55 | ] 56 | } 57 | 58 | static identity() { 59 | return [ 60 | 1, 0, 0, 0, 61 | 0, 1, 0, 0, 62 | 0, 0, 1, 0, 63 | 0, 0, 0, 1 64 | ] 65 | } 66 | 67 | static inverse(m) { 68 | const result = new Float32Array(16) 69 | const m00 = m[0 * 4 + 0] 70 | const m01 = m[0 * 4 + 1] 71 | const m02 = m[0 * 4 + 2] 72 | const m03 = m[0 * 4 + 3] 73 | const m10 = m[1 * 4 + 0] 74 | const m11 = m[1 * 4 + 1] 75 | const m12 = m[1 * 4 + 2] 76 | const m13 = m[1 * 4 + 3] 77 | const m20 = m[2 * 4 + 0] 78 | const m21 = m[2 * 4 + 1] 79 | const m22 = m[2 * 4 + 2] 80 | const m23 = m[2 * 4 + 3] 81 | const m30 = m[3 * 4 + 0] 82 | const m31 = m[3 * 4 + 1] 83 | const m32 = m[3 * 4 + 2] 84 | const m33 = m[3 * 4 + 3] 85 | const tmp_0 = m22 * m33 86 | const tmp_1 = m32 * m23 87 | const tmp_2 = m12 * m33 88 | const tmp_3 = m32 * m13 89 | const tmp_4 = m12 * m23 90 | const tmp_5 = m22 * m13 91 | const tmp_6 = m02 * m33 92 | const tmp_7 = m32 * m03 93 | const tmp_8 = m02 * m23 94 | const tmp_9 = m22 * m03 95 | const tmp_10 = m02 * m13 96 | const tmp_11 = m12 * m03 97 | const tmp_12 = m20 * m31 98 | const tmp_13 = m30 * m21 99 | const tmp_14 = m10 * m31 100 | const tmp_15 = m30 * m11 101 | const tmp_16 = m10 * m21 102 | const tmp_17 = m20 * m11 103 | const tmp_18 = m00 * m31 104 | const tmp_19 = m30 * m01 105 | const tmp_20 = m00 * m21 106 | const tmp_21 = m20 * m01 107 | const tmp_22 = m00 * m11 108 | const tmp_23 = m10 * m01 109 | 110 | const t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - 111 | (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31) 112 | const t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - 113 | (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31) 114 | const t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - 115 | (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31) 116 | const t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - 117 | (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21) 118 | 119 | const d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3) 120 | 121 | result[0] = d * t0 122 | result[1] = d * t1 123 | result[2] = d * t2 124 | result[3] = d * t3 125 | result[4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - 126 | (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)) 127 | result[5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - 128 | (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)) 129 | result[6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - 130 | (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)) 131 | result[7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - 132 | (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)) 133 | result[8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - 134 | (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)) 135 | result[9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - 136 | (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)) 137 | result[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - 138 | (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)) 139 | result[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - 140 | (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)) 141 | result[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - 142 | (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)) 143 | result[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - 144 | (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)) 145 | result[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - 146 | (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)) 147 | result[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - 148 | (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)) 149 | 150 | return result 151 | } 152 | 153 | static transpose(m) { 154 | return [ 155 | m[0], m[4], m[8], m[12], 156 | m[1], m[5], m[9], m[13], 157 | m[2], m[6], m[10], m[14], 158 | m[3], m[7], m[11], m[15], 159 | ] 160 | } 161 | 162 | static translate(tx, ty, tz) { 163 | return [ 164 | 1, 0, 0, 0, 165 | 0, 1, 0, 0, 166 | 0, 0, 1, 0, 167 | tx, ty, tz, 1 168 | ] 169 | } 170 | 171 | static rotateX(angle) { 172 | const radians = angle * Math.PI / 180 173 | const cos = Math.cos(radians) 174 | const sin = Math.sin(radians) 175 | return [ 176 | 1, 0, 0, 0, 177 | 0, cos, sin, 0, 178 | 0, -sin, cos, 0, 179 | 0, 0, 0, 1 180 | ] 181 | } 182 | 183 | static rotateY(angle) { 184 | const radians = angle * Math.PI / 180 185 | const cos = Math.cos(radians) 186 | const sin = Math.sin(radians) 187 | return [ 188 | cos, 0, -sin, 0, 189 | 0, 1, 0, 0, 190 | sin, 0, cos, 0, 191 | 0, 0, 0, 1 192 | ] 193 | } 194 | 195 | static rotateZ(angle) { 196 | const radians = angle * Math.PI / 180 197 | const cos = Math.cos(radians) 198 | const sin = Math.sin(radians) 199 | return [ 200 | cos, sin, 0, 0, 201 | -sin, cos, 0, 0, 202 | 0, 0, 1, 0, 203 | 0, 0, 0, 1 204 | ] 205 | } 206 | 207 | static scale(sx, sy, sz) { 208 | return [ 209 | sx, 0, 0, 0, 210 | 0, sy, 0, 0, 211 | 0, 0, sz, 0, 212 | 0, 0, 0, 1 213 | ] 214 | } 215 | 216 | static lookAt(viewer, target) { 217 | const z = Vector.normalize(Vector.subtract(viewer, target)) 218 | const x = Vector.normalize(Vector.cross([0, 1, 0], z)) 219 | const y = Vector.normalize(Vector.cross(z, x)) 220 | 221 | return [ 222 | x[0], x[1], x[2], 0, 223 | y[0], y[1], y[2], 0, 224 | z[0], z[1], z[2], 0, 225 | viewer[0], viewer[1], viewer[2], 1 226 | ] 227 | } 228 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sandbox 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |

Sandbox

15 |

A simple WebGL library for creating 3D scenes.

16 |
17 |
18 |

Primitives

19 |
20 |
21 |
22 | 23 |

Plane

24 |
25 |

Sandbox.Plane(width, height, widthSegments, heightSegments)

26 |
27 |
28 |
29 |
30 | 31 |

Circle

32 |
33 |

Sandbox.Circle(radius, segments)

34 |
35 |
36 |
37 |
38 | 39 |

Tetrahedron

40 |
41 |

Sandbox.Tetrahedron(size)

42 |
43 |
44 |
45 |
46 | 47 |

Cube

48 |
49 |

Sandbox.Cube(width, height, depth, widthSegments, heightSegments, depthSegments)

50 |
51 |
52 |
53 |
54 | 55 |

Sphere

56 |
57 |

Sandbox.Sphere(radius, segments)

58 |
59 |
60 |
61 |
62 | 63 |

Cylinder

64 |
65 |

Sandbox.Cylinder(radius, height, segments)

66 |
67 |
68 |
69 |
70 |
71 |

Cameras

72 |
73 |
74 | 75 |

Perspective

76 |
77 |

Sandbox.Perspective(fieldOfView, aspectRatio, near, far)

78 |
79 |
80 | 81 | 82 |
83 |
84 | 85 | 86 |
87 |
88 | 89 | 90 |
91 |
92 | 93 | 94 |
95 |
96 |
97 |
98 | 99 |

Orthographic

100 |
101 |

Sandbox.Orthographic(left, right, bottom, top, near, far)

102 |
103 |
104 | 105 | 106 |
107 |
108 | 109 | 110 |
111 |
112 | 113 | 114 |
115 |
116 | 117 | 118 |
119 |
120 | 121 | 122 |
123 |
124 | 125 | 126 |
127 |
128 |
129 |
130 |
131 |
132 | 133 | 134 | -------------------------------------------------------------------------------- /src/css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | font-family: system-ui, sans-serif; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | :root { 9 | --green-100: #ECFEEF; 10 | --green-200: #A2F6CF; 11 | --green-300: #6BD490; 12 | --green-400: #417944; 13 | --green-500: #0F270C; 14 | --green-600: #252825; 15 | --grey-100: #F9F9F9; 16 | --grey-200: #C4C4C4; 17 | --grey-300: #808080; 18 | --grey-400: #484848; 19 | --grey-500: #1B1B1B; 20 | --grey-600: #242424; 21 | } 22 | 23 | main { 24 | background-color: var(--grey-100); 25 | min-height: 100vh; 26 | } 27 | 28 | section { 29 | position: relative; 30 | width: 100%; 31 | } 32 | 33 | section.hero { 34 | align-items: center; 35 | display: flex; 36 | flex-direction: column; 37 | height: 50vh; 38 | justify-content: center; 39 | min-height: 600px; 40 | } 41 | 42 | h1 { 43 | align-items: center; 44 | color: var(--green-500); 45 | display: flex; 46 | flex-direction: column; 47 | font-family: system-ui, sans-serif; 48 | font-size: 96px; 49 | font-weight: 700; 50 | letter-spacing: -1px; 51 | line-height: 1; 52 | text-align: center; 53 | } 54 | 55 | h1 figure { 56 | background-image: url('../../documentation/assets/img/icon.png'); 57 | background-position: center center; 58 | background-repeat: no-repeat; 59 | background-size: contain; 60 | display: block; 61 | height: 120px; 62 | margin-bottom: 16px; 63 | width: 120px; 64 | } 65 | 66 | p { 67 | color: var(--green-300); 68 | font-family: system-ui, sans-serif; 69 | font-size: 32px; 70 | font-weight: 500; 71 | letter-spacing: -1px; 72 | line-height: 1.25; 73 | margin-top: 16px; 74 | max-width: 720px; 75 | text-align: center; 76 | width: 100%; 77 | } 78 | 79 | section.primitives, section.cameras { 80 | align-items: center; 81 | background-color: var(--green-100); 82 | display: flex; 83 | flex-direction: column; 84 | min-height: 900px; 85 | padding: 120px 20px; 86 | } 87 | 88 | section.cameras { 89 | background-color: transparent; 90 | } 91 | 92 | h2 { 93 | color: var(--green-500); 94 | font-family: system-ui, sans-serif; 95 | font-size: 48px; 96 | font-weight: 700; 97 | letter-spacing: -1px; 98 | line-height: 1; 99 | margin-bottom: 96px; 100 | text-align: center; 101 | } 102 | 103 | .primitive-cards, .cameras-wrap { 104 | display: flex; 105 | flex-wrap: wrap; 106 | justify-content: space-between; 107 | margin-bottom: 120px; 108 | max-width: 1200px; 109 | width: 100%; 110 | } 111 | 112 | .card-wrap { 113 | background-color: var(--green-200); 114 | border-radius: 48px; 115 | margin-bottom: 32px; 116 | padding: 16px; 117 | width: calc(33.33333% - 12px); 118 | } 119 | 120 | .primitive--card canvas { 121 | aspect-ratio: 1; 122 | border-radius: 24px; 123 | height: auto; 124 | margin-bottom: 16px; 125 | width: 100%; 126 | } 127 | 128 | .primitive--card { 129 | align-items: flex-start; 130 | background-color: white; 131 | border-radius: 32px; 132 | color: var(--grey-600); 133 | display: flex; 134 | flex-direction: column; 135 | font-family: system-ui, sans-serif; 136 | height: 100%; 137 | padding: 32px 32px 24px 32px; 138 | text-align: left; 139 | width: 100%; 140 | } 141 | 142 | button { 143 | background-color: var(--green-500); 144 | border: none; 145 | border-radius: 12px; 146 | color: var(--green-100); 147 | cursor: pointer; 148 | display: block; 149 | font-family: system-ui, sans-serif; 150 | font-size: 16px; 151 | font-weight: 600; 152 | height: 44px; 153 | margin-bottom: 8px; 154 | margin-left: 4px; 155 | margin-right: 4px; 156 | padding: 12px 28px; 157 | text-decoration: none; 158 | transition: background-color 200ms cubic-bezier(0.5, 1, 0.5, 1), color 200ms cubic-bezier(0.5, 1, 0.5, 1); 159 | } 160 | 161 | button:hover, button:focus { 162 | background-color: var(--green-400); 163 | color: var(--green-100); 164 | } 165 | 166 | button:focus { 167 | box-shadow: 0px 0px 0px 4px var(--green-200), 0px 0px 0px 8px white; 168 | outline: none; 169 | } 170 | 171 | button:disabled { 172 | background-color: var(--green-400); 173 | color: var(--green-100); 174 | cursor: initial; 175 | opacity: 0.25; 176 | } 177 | 178 | h4 { 179 | font-size: 24px; 180 | font-weight: 600; 181 | margin-bottom: 16px; 182 | transition: color 400ms ease; 183 | } 184 | 185 | hr { 186 | background-color: var(--grey-200); 187 | border: none; 188 | height: 1px; 189 | margin-top: 0; 190 | width: 100%; 191 | } 192 | 193 | section.primitives .card-wrap p, section.cameras .camera--perspective p, section.cameras .camera--orthographic p { 194 | color: black; 195 | font-size: 16px; 196 | font-weight: 400; 197 | letter-spacing: 0; 198 | line-height: 1.5; 199 | margin-bottom: 8px; 200 | text-align: left; 201 | width: 100%; 202 | } 203 | 204 | h3 { 205 | color: var(--green-500); 206 | font-family: system-ui, sans-serif; 207 | font-size: 32px; 208 | font-weight: 700; 209 | letter-spacing: -1px; 210 | line-height: 1; 211 | margin-bottom: 16px; 212 | margin-top: 96px; 213 | text-align: center; 214 | } 215 | 216 | p { 217 | color: var(--green-400); 218 | font-family: system-ui, sans-serif; 219 | font-size: 20px; 220 | font-weight: 500; 221 | letter-spacing: 0; 222 | line-height: 1.25; 223 | margin-top: 16px; 224 | margin-bottom: 96px; 225 | max-width: 720px; 226 | text-align: center; 227 | width: 100%; 228 | } 229 | 230 | .camera--perspective, .camera--orthographic { 231 | background-color: var(--green-200); 232 | border-radius: 48px; 233 | margin-bottom: 96px; 234 | padding: 32px 32px 24px 32px; 235 | width: 100%; 236 | } 237 | 238 | .camera--orthographic { 239 | margin-bottom: 0; 240 | } 241 | 242 | .camera--perspective canvas, .camera--orthographic canvas { 243 | border-radius: 24px; 244 | height: 512px; 245 | margin-bottom: 16px; 246 | width: 100%; 247 | } 248 | 249 | .camera--perspective h4, .camera--orthographic h4 { 250 | color: var(--green-500); 251 | } 252 | 253 | .camera--perspective hr, .camera--orthographic hr { 254 | background-color: var(--green-300); 255 | } 256 | 257 | section.cameras .camera--perspective p, section.cameras .camera--orthographic p { 258 | color: var(--green-500); 259 | margin-bottom: 16px; 260 | } 261 | 262 | .control-wrap { 263 | background-color: var(--green-100); 264 | border-radius: 24px; 265 | display: flex; 266 | flex-direction: column; 267 | margin-bottom: 8px; 268 | padding: 16px; 269 | width: 100%; 270 | } 271 | 272 | .control-item { 273 | display: flex; 274 | margin: 8px 0px; 275 | padding: 0 8px; 276 | } 277 | 278 | .control-item label { 279 | margin-right: 32px; 280 | text-align: right; 281 | width: 100px; 282 | } 283 | 284 | input[type="range"] { 285 | background-color: transparent; 286 | left: 32px; 287 | bottom: 48px; 288 | display: block; 289 | margin-left: auto; 290 | margin-right: auto; 291 | max-width: 100%; 292 | -webkit-appearance: none; 293 | width: calc(100% - 132px); 294 | z-index: 3; 295 | } 296 | 297 | input[type="range"]::-webkit-slider-runnable-track { 298 | background-color: var(--green-200); 299 | border-radius: 2px; 300 | height: 4px; 301 | max-width: 100%; 302 | width: 512px; 303 | } 304 | 305 | input[type="range"]::-webkit-slider-thumb { 306 | background-color: var(--green-400); 307 | border-radius: 50%; 308 | cursor: pointer; 309 | height: 32px; 310 | margin-top: -14px; 311 | -webkit-appearance: none; 312 | width: 32px; 313 | } -------------------------------------------------------------------------------- /src/js/demo/main.js: -------------------------------------------------------------------------------- 1 | import Sandbox from '../../../dist/js/sandbox.min.js' 2 | import primitiveVertex from '../../shaders/primitive/vertex.glsl' 3 | import primitiveFragment from '../../shaders/primitive/fragment.glsl' 4 | 5 | let now = 0 6 | let time = 0 7 | let then = 0 8 | 9 | /* 10 | Primitives 11 | */ 12 | 13 | //Plane 14 | const planeCanvas = document.getElementById('primitive--plane') 15 | const planeRenderer = new Sandbox.Renderer(planeCanvas) 16 | planeRenderer.resize() 17 | const planeVolume = new Sandbox.Volume() 18 | 19 | const planeCamera = new Sandbox.Perspective(35, 1, 0.1, 100) 20 | planeCamera.setPosition(0, 0, 6) 21 | const planeShader = new Sandbox.Program(planeRenderer.gl, primitiveVertex, primitiveFragment) 22 | 23 | const planeGeometry = new Sandbox.Plane(2, 2, 1, 1) 24 | const planeMesh = new Sandbox.Mesh(planeGeometry, planeShader) 25 | planeVolume.add(planeMesh) 26 | 27 | //Circle 28 | const circleCanvas = document.getElementById('primitive--circle') 29 | const circleRenderer = new Sandbox.Renderer(circleCanvas) 30 | circleRenderer.resize() 31 | const circleVolume = new Sandbox.Volume() 32 | 33 | const circleCamera = new Sandbox.Perspective(35, 1, 0.1, 100) 34 | circleCamera.setPosition(0, 0, 6) 35 | const circleShader = new Sandbox.Program(circleRenderer.gl, primitiveVertex, primitiveFragment) 36 | 37 | const circleGeometry = new Sandbox.Circle(1, 64) 38 | const circleMesh = new Sandbox.Mesh(circleGeometry, circleShader) 39 | circleVolume.add(circleMesh) 40 | 41 | //Tetrahedron 42 | const tetrahedronCanvas = document.getElementById('primitive--tetrahedron') 43 | const tetrahedronRenderer = new Sandbox.Renderer(tetrahedronCanvas) 44 | tetrahedronRenderer.resize() 45 | const tetrahedronVolume = new Sandbox.Volume() 46 | 47 | const tetrahedronCamera = new Sandbox.Perspective(35, 1, 0.1, 100) 48 | tetrahedronCamera.setPosition(0, 0, 6) 49 | const tetrahedronShader = new Sandbox.Program(tetrahedronRenderer.gl, primitiveVertex, primitiveFragment) 50 | 51 | const tetrahedronGeometry = new Sandbox.Tetrahedron(2) 52 | const tetrahedronMesh = new Sandbox.Mesh(tetrahedronGeometry, tetrahedronShader) 53 | tetrahedronVolume.add(tetrahedronMesh) 54 | 55 | //Cube 56 | const cubeCanvas = document.getElementById('primitive--cube') 57 | const cubeRenderer = new Sandbox.Renderer(cubeCanvas) 58 | cubeRenderer.resize() 59 | const cubeVolume = new Sandbox.Volume() 60 | 61 | const cubeCamera = new Sandbox.Perspective(35, 1, 0.1, 100) 62 | cubeCamera.setPosition(0, 0, 6) 63 | const cubeShader = new Sandbox.Program(cubeRenderer.gl, primitiveVertex, primitiveFragment) 64 | 65 | const cubeGeometry = new Sandbox.Cube(2, 2, 2, 1, 1, 1) 66 | const cubeMesh = new Sandbox.Mesh(cubeGeometry, cubeShader) 67 | cubeVolume.add(cubeMesh) 68 | 69 | //Sphere 70 | const sphereCanvas = document.getElementById('primitive--sphere') 71 | const sphereRenderer = new Sandbox.Renderer(sphereCanvas) 72 | sphereRenderer.resize() 73 | const sphereVolume = new Sandbox.Volume() 74 | 75 | const sphereCamera = new Sandbox.Perspective(35, 1, 0.1, 100) 76 | sphereCamera.setPosition(0, 0, 6) 77 | const sphereShader = new Sandbox.Program(sphereRenderer.gl, primitiveVertex, primitiveFragment) 78 | 79 | const sphereGeometry = new Sandbox.Sphere(1, 64) 80 | const sphereMesh = new Sandbox.Mesh(sphereGeometry, sphereShader) 81 | sphereVolume.add(sphereMesh) 82 | 83 | //Cylinder 84 | const cylinderCanvas = document.getElementById('primitive--cylinder') 85 | const cylinderRenderer = new Sandbox.Renderer(cylinderCanvas) 86 | cylinderRenderer.resize() 87 | const cylinderVolume = new Sandbox.Volume() 88 | 89 | const cylinderCamera = new Sandbox.Perspective(35, 1, 0.1, 100) 90 | cylinderCamera.setPosition(0, 0, 6) 91 | const cylinderShader = new Sandbox.Program(cylinderRenderer.gl, primitiveVertex, primitiveFragment) 92 | 93 | const cylinderGeometry = new Sandbox.Cylinder(1, 2, 64) 94 | const cylinderMesh = new Sandbox.Mesh(cylinderGeometry, cylinderShader) 95 | cylinderVolume.add(cylinderMesh) 96 | 97 | 98 | /* 99 | Cameras 100 | */ 101 | 102 | //Perspective 103 | const perspectiveCanvas = document.getElementById('perspective') 104 | const perspectiveRenderer = new Sandbox.Renderer(perspectiveCanvas) 105 | perspectiveRenderer.resize() 106 | const perspectiveVolume = new Sandbox.Volume() 107 | 108 | const perspectiveCamera = new Sandbox.Perspective(35, 1.85, 0.1, 100) 109 | perspectiveCamera.setPosition(0, 0, 6) 110 | const perspectiveShader = new Sandbox.Program(perspectiveRenderer.gl, primitiveVertex, primitiveFragment) 111 | 112 | const perspectiveGeometry = new Sandbox.Cube(2, 2, 2, 1, 1, 1) 113 | const perspectiveMesh = new Sandbox.Mesh(perspectiveGeometry, perspectiveShader) 114 | perspectiveVolume.add(perspectiveMesh) 115 | 116 | const perspectiveFOV = document.getElementById('perspective-fov') 117 | perspectiveFOV.addEventListener('input', (event) => { 118 | perspectiveCamera.setFieldOfView(event.target.value) 119 | }) 120 | 121 | const perspectiveAspect = document.getElementById('perspective-aspect') 122 | perspectiveAspect.addEventListener('input', (event) => { 123 | perspectiveCamera.setAspectRatio(event.target.value) 124 | }) 125 | 126 | const perspectiveNear = document.getElementById('perspective-near') 127 | perspectiveNear.addEventListener('input', (event) => { 128 | perspectiveCamera.setNear(parseFloat(event.target.value)) 129 | }) 130 | 131 | const perspectiveFar = document.getElementById('perspective-far') 132 | perspectiveFar.addEventListener('input', (event) => { 133 | perspectiveCamera.setFar(parseFloat(event.target.value)) 134 | }) 135 | 136 | //Orthographic 137 | const orthographicCanvas = document.getElementById('orthographic') 138 | const orthographicRenderer = new Sandbox.Renderer(orthographicCanvas) 139 | orthographicRenderer.resize() 140 | const orthographicVolume = new Sandbox.Volume() 141 | 142 | const orthographicCamera = new Sandbox.Orthographic(-4.43359375, 4.43359375, -2, 2, -2, 2) 143 | //orthographicCamera.setPosition(0, 0, -6) 144 | const orthographicShader = new Sandbox.Program(orthographicRenderer.gl, primitiveVertex, primitiveFragment) 145 | 146 | const orthographicGeometry = new Sandbox.Cube(2, 2, 2, 1, 1, 1) 147 | const orthographicMesh = new Sandbox.Mesh(orthographicGeometry, orthographicShader) 148 | orthographicVolume.add(orthographicMesh) 149 | 150 | const orthographicLeft = document.getElementById('orthographic-left') 151 | orthographicLeft.addEventListener('input', (event) => { 152 | orthographicCamera.setLeft(parseFloat(event.target.value)) 153 | }) 154 | 155 | const orthographicRight = document.getElementById('orthographic-right') 156 | orthographicRight.addEventListener('input', (event) => { 157 | orthographicCamera.setRight(parseFloat(event.target.value)) 158 | }) 159 | 160 | const orthographicBottom = document.getElementById('orthographic-bottom') 161 | orthographicBottom.addEventListener('input', (event) => { 162 | orthographicCamera.setBottom(parseFloat(event.target.value)) 163 | }) 164 | 165 | const orthographicTop = document.getElementById('orthographic-top') 166 | orthographicTop.addEventListener('input', (event) => { 167 | orthographicCamera.setTop(parseFloat(event.target.value)) 168 | }) 169 | 170 | const orthographicNear = document.getElementById('orthographic-near') 171 | orthographicNear.addEventListener('input', (event) => { 172 | orthographicCamera.setNear(parseFloat(event.target.value)) 173 | }) 174 | 175 | const orthographicFar = document.getElementById('orthographic-far') 176 | orthographicFar.addEventListener('input', (event) => { 177 | orthographicCamera.setFar(parseFloat(event.target.value)) 178 | }) 179 | 180 | const update = (current) => { 181 | now = current 182 | time += (now - then) 183 | then = now 184 | 185 | //Plane 186 | planeMesh.setRotationX(Math.sin(time / 1000) * 30) 187 | planeMesh.setRotationY(Math.cos(time / 1200) * 30) 188 | planeRenderer.gl.clearColor(0, 0, 0, 0) 189 | planeRenderer.render(planeVolume, planeCamera) 190 | 191 | //Circle 192 | circleMesh.setRotationX(Math.sin(time / 1000) * 30) 193 | circleMesh.setRotationY(Math.cos(time / 1200) * 30) 194 | circleRenderer.gl.clearColor(0, 0, 0, 0) 195 | circleRenderer.render(circleVolume, circleCamera) 196 | 197 | //Tetrahedron 198 | tetrahedronMesh.setRotationX(Math.sin(time / 1000) * 30) 199 | tetrahedronMesh.setRotationY(Math.cos(time / 1200) * 30) 200 | tetrahedronRenderer.gl.clearColor(0, 0, 0, 0) 201 | tetrahedronRenderer.render(tetrahedronVolume, tetrahedronCamera) 202 | 203 | //Cube 204 | cubeMesh.setRotationX(Math.sin(time / 1000) * 30) 205 | cubeMesh.setRotationY(Math.cos(time / 1200) * 30) 206 | cubeRenderer.gl.clearColor(0, 0, 0, 0) 207 | cubeRenderer.render(cubeVolume, cubeCamera) 208 | 209 | //Sphere 210 | sphereMesh.setRotationX(Math.sin(time / 1000) * 30) 211 | sphereMesh.setRotationY(Math.cos(time / 1200) * 30) 212 | sphereRenderer.gl.clearColor(0, 0, 0, 0) 213 | sphereRenderer.render(sphereVolume, sphereCamera) 214 | 215 | //Cylinder 216 | cylinderMesh.setRotationX(Math.sin(time / 1000) * 30) 217 | cylinderMesh.setRotationY(Math.cos(time / 1200) * 30) 218 | cylinderRenderer.gl.clearColor(0, 0, 0, 0) 219 | cylinderRenderer.render(cylinderVolume, cylinderCamera) 220 | 221 | //Perspective Camera 222 | perspectiveMesh.setRotationX(Math.sin(time / 1000) * 30) 223 | perspectiveMesh.setRotationY(Math.cos(time / 1200) * 30) 224 | perspectiveRenderer.gl.clearColor(1, 1, 1, 1) 225 | perspectiveRenderer.render(perspectiveVolume, perspectiveCamera) 226 | 227 | //Orthographic Camera 228 | orthographicMesh.setRotationX(Math.sin(time / 1000) * 30) 229 | orthographicMesh.setRotationY(Math.cos(time / 1200) * 30) 230 | orthographicRenderer.gl.clearColor(1, 1, 1, 1) 231 | orthographicRenderer.render(orthographicVolume, orthographicCamera) 232 | 233 | window.requestAnimationFrame(update) 234 | } 235 | 236 | window.requestAnimationFrame(update) -------------------------------------------------------------------------------- /dist/js/sandbox.min.js: -------------------------------------------------------------------------------- 1 | var gt=Object.defineProperty;var dt=M=>gt(M,"__esModule",{value:!0});var bt=(M,t)=>{dt(M);for(var s in t)gt(M,s,{get:t[s],enumerable:!0})};bt(exports,{default:()=>E});var Z=class{constructor(t){this.gl=t.getContext("webgl",{powerPreference:"high-performance"}),this.resize=this.resize.bind(this),this.render=this.render.bind(this),this.depthTest=!0,this.faceCulling=!0,this.pixelRatio=Math.min(window.devicePixelRatio,2),this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!0),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.framebuffer=null}setPixelRatio(t){this.pixelRatio=t}setFrameBuffer(t){t!==null?this.framebuffer=t.buffer:this.framebuffer=null}resize(){let t=this.gl.canvas.clientWidth*this.pixelRatio,s=this.gl.canvas.clientHeight*this.pixelRatio;return this.gl.canvas.width*this.pixelRatio!==t||this.gl.canvas.height*this.pixelRatio!==s?(this.gl.canvas.width=t,this.gl.canvas.height=s,this.gl.viewport(0,0,this.gl.canvas.width,this.gl.canvas.height),!0):!1}render(t,s){this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,this.framebuffer),this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT),this.faceCulling&&this.gl.enable(this.gl.CULL_FACE),this.depthTest&&this.gl.enable(this.gl.DEPTH_TEST),this.gl.enable(this.gl.BLEND),this.gl.blendEquation(this.gl.FUNC_ADD),this.gl.blendFunc(this.gl.ONE_MINUS_CONSTANT_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA);let i=null,r=null;s.setViewProjectionMatrix();for(let e of t.objects){e.setProjectionMatrix(s.viewProjectionMatrix);let h=!1;if(e.shader.program!==i&&(this.gl.useProgram(e.shader.program),i=e.shader.program,h=!0),h||e.attributes!=r){for(let o in e.attributes){this.gl.enableVertexAttribArray(e.attributes[o].location),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,e.attributes[o].buffer);let l=e.attributes[o].size,p=this.gl.FLOAT,m=!1,x=0,g=0;this.gl.vertexAttribPointer(e.attributes[o].location,l,p,m,x,g)}r=e.attributes}for(let o in e.uniforms)if(o==="uViewProjectionMatrix")this.gl.uniformMatrix4fv(e.uniforms[o].location,!1,e.projectionMatrix);else if(o==="uNormalMatrix")this.gl.uniformMatrix4fv(e.uniforms[o].location,!1,e.normalMatrix);else if(o==="uLocalMatrix")this.gl.uniformMatrix4fv(e.uniforms[o].location,!1,e.localMatrix);else switch(e.uniforms[o].type){case"1f":this.gl.uniform1f(e.uniforms[o].location,e.uniforms[o].value);break;case"2f":this.gl.uniform2f(e.uniforms[o].location,e.uniforms[o].value[0],e.uniforms[o].value[1]);break;case"3f":this.gl.uniform3f(e.uniforms[o].location,e.uniforms[o].value[0],e.uniforms[o].value[1],e.uniforms[o].value[2]);break;case"4f":this.gl.uniform4f(e.uniforms[o].location,e.uniforms[o].value[0],e.uniforms[o].value[1],e.uniforms[o].value[2],e.uniforms[o].value[3]);break;case"mat3":this.gl.uniformMatrix3fv(e.uniforms[o].location,!1,e.uniforms[o].value);break;case"mat4":this.gl.uniformMatrix4fv(e.uniforms[o].location,!1,e.uniforms[o].value);break;case"tex":this.gl.uniform1i(e.uniforms[o].location,e.uniforms[o].value.id),this.gl.activeTexture(this.gl.TEXTURE0+e.uniforms[o].value.id),this.gl.bindTexture(this.gl.TEXTURE_2D,e.uniforms[o].value.texture);default:break}let n=this.gl[e.drawMode],a=0,u=e.attributes.aPosition.count;this.gl.drawArrays(n,a,u)}}};var B=class{static cross(t,s){return[t[1]*s[2]-t[2]*s[1],t[2]*s[0]-t[0]*s[2],t[0]*s[1]-t[1]*s[0]]}static subtract(t,s){return[t[0]-s[0],t[1]-s[1],t[2]-s[2]]}static normalize(t){let s=Math.sqrt(t[0]**2+t[1]**2+t[2]**2);return s>1e-5?[t[0]/s,t[1]/s,t[2]/s]:[0,0,0]}};var f=class{static multiply(t,s){let i=s[0*4+0],r=s[0*4+1],e=s[0*4+2],h=s[0*4+3],n=s[1*4+0],a=s[1*4+1],u=s[1*4+2],o=s[1*4+3],l=s[2*4+0],p=s[2*4+1],m=s[2*4+2],x=s[2*4+3],g=s[3*4+0],b=s[3*4+1],y=s[3*4+2],_=s[3*4+3],P=t[0*4+0],j=t[0*4+1],N=t[0*4+2],U=t[0*4+3],d=t[1*4+0],R=t[1*4+1],T=t[1*4+2],c=t[1*4+3],A=t[2*4+0],w=t[2*4+1],I=t[2*4+2],F=t[2*4+3],v=t[3*4+0],X=t[3*4+1],k=t[3*4+2],G=t[3*4+3];return[i*P+r*d+e*A+h*v,i*j+r*R+e*w+h*X,i*N+r*T+e*I+h*k,i*U+r*c+e*F+h*G,n*P+a*d+u*A+o*v,n*j+a*R+u*w+o*X,n*N+a*T+u*I+o*k,n*U+a*c+u*F+o*G,l*P+p*d+m*A+x*v,l*j+p*R+m*w+x*X,l*N+p*T+m*I+x*k,l*U+p*c+m*F+x*G,g*P+b*d+y*A+_*v,g*j+b*R+y*w+_*X,g*N+b*T+y*I+_*k,g*U+b*c+y*F+_*G]}static identity(){return[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}static inverse(t){let s=new Float32Array(16),i=t[0*4+0],r=t[0*4+1],e=t[0*4+2],h=t[0*4+3],n=t[1*4+0],a=t[1*4+1],u=t[1*4+2],o=t[1*4+3],l=t[2*4+0],p=t[2*4+1],m=t[2*4+2],x=t[2*4+3],g=t[3*4+0],b=t[3*4+1],y=t[3*4+2],_=t[3*4+3],P=m*_,j=y*x,N=u*_,U=y*o,d=u*x,R=m*o,T=e*_,c=y*h,A=e*x,w=m*h,I=e*o,F=u*h,v=l*b,X=g*p,k=n*b,G=g*a,S=n*p,q=l*a,L=i*b,z=g*r,V=i*p,O=l*r,Y=i*a,H=n*r,ft=P*a+U*p+d*b-(j*a+N*p+R*b),pt=j*r+T*p+w*b-(P*r+c*p+A*b),mt=N*r+c*a+I*b-(U*r+T*a+F*b),xt=R*r+A*a+F*p-(d*r+w*a+I*p),D=1/(i*ft+n*pt+l*mt+g*xt);return s[0]=D*ft,s[1]=D*pt,s[2]=D*mt,s[3]=D*xt,s[4]=D*(j*n+N*l+R*g-(P*n+U*l+d*g)),s[5]=D*(P*i+c*l+A*g-(j*i+T*l+w*g)),s[6]=D*(U*i+T*n+F*g-(N*i+c*n+I*g)),s[7]=D*(d*i+w*n+I*l-(R*i+A*n+F*l)),s[8]=D*(v*o+G*x+S*_-(X*o+k*x+q*_)),s[9]=D*(X*h+L*x+O*_-(v*h+z*x+V*_)),s[10]=D*(k*h+z*o+Y*_-(G*h+L*o+H*_)),s[11]=D*(q*h+V*o+H*x-(S*h+O*o+Y*x)),s[12]=D*(k*m+q*y+X*u-(S*y+v*u+G*m)),s[13]=D*(V*y+v*e+z*m-(L*m+O*y+X*e)),s[14]=D*(L*u+H*y+G*e-(Y*y+k*e+z*u)),s[15]=D*(Y*m+S*e+O*u-(V*u+H*m+q*e)),s}static transpose(t){return[t[0],t[4],t[8],t[12],t[1],t[5],t[9],t[13],t[2],t[6],t[10],t[14],t[3],t[7],t[11],t[15]]}static translate(t,s,i){return[1,0,0,0,0,1,0,0,0,0,1,0,t,s,i,1]}static rotateX(t){let s=t*Math.PI/180,i=Math.cos(s),r=Math.sin(s);return[1,0,0,0,0,i,r,0,0,-r,i,0,0,0,0,1]}static rotateY(t){let s=t*Math.PI/180,i=Math.cos(s),r=Math.sin(s);return[i,0,-r,0,0,1,0,0,r,0,i,0,0,0,0,1]}static rotateZ(t){let s=t*Math.PI/180,i=Math.cos(s),r=Math.sin(s);return[i,r,0,0,-r,i,0,0,0,0,1,0,0,0,0,1]}static scale(t,s,i){return[t,0,0,0,0,s,0,0,0,0,i,0,0,0,0,1]}static lookAt(t,s){let i=B.normalize(B.subtract(t,s)),r=B.normalize(B.cross([0,1,0],i)),e=B.normalize(B.cross(i,r));return[r[0],r[1],r[2],0,e[0],e[1],e[2],0,i[0],i[1],i[2],0,t[0],t[1],t[2],1]}};var W=class{constructor(t,s,i,r,e,h){this.left=t,this.right=s,this.bottom=i,this.top=r,this.near=e,this.far=h,this.type="orthographic",this.position={x:0,y:0,z:0},this.rotation={x:0,y:0,z:0},this.viewMatrix=f.identity(),this.createMatrix()}createMatrix(){this.matrix=[2/(this.right-this.left),0,0,0,0,2/(this.top-this.bottom),0,0,0,0,-2/(this.far-this.near),0,-(this.right+this.left)/(this.right-this.left),-(this.top+this.bottom)/(this.top-this.bottom),-(this.far+this.near)/(this.far-this.near),1]}_recalculateViewMatrix(){let t=f.identity(),s=f.translate(this.position.x,this.position.y,this.position.z),i=f.rotateX(this.rotation.x),r=f.rotateY(this.rotation.y),e=f.rotateZ(this.rotation.z),h=f.multiply(t,s);h=f.multiply(h,i),h=f.multiply(h,r),h=f.multiply(h,e),this.lookAtEnabled&&(h=f.lookAt([h[12],h[13],h[14]],[this.lookAtTarget.localMatrix[12],this.lookAtTarget.localMatrix[13],this.lookAtTarget.localMatrix[14]])),this.viewMatrix=f.inverse(h)}setViewProjectionMatrix(){this._recalculateViewMatrix(),this.viewProjectionMatrix=f.multiply(this.matrix,this.viewMatrix)}setLeft(t){this.left=t,this.createMatrix()}setRight(t){this.right=t,this.createMatrix()}setBottom(t){this.bottom=t,this.createMatrix()}setTop(t){this.top=t,this.createMatrix()}setNear(t){this.near=t,this.createMatrix()}setFar(t){this.far=t,this.createMatrix()}setPosition(t,s,i){this.position={x:t,y:s,z:i}}setRotationX(t){this.rotation.x=t}setRotationY(t){this.rotation.y=t}setRotationZ(t){this.rotation.z=t}lookAt(t){this.lookAtEnabled=!0,this.lookAtTarget=t}};var K=class{constructor(t,s,i,r){this.fieldOfView=t*Math.PI/180,this.aspectRatio=s,this.near=i,this.far=r,this.type="perspective",this.position={x:0,y:0,z:0},this.rotation={x:0,y:0,z:0},this.viewMatrix=f.identity(),this.lookAtEnabled=!1,this.createMatrix()}createMatrix(){this.top=this.near*Math.tan(this.fieldOfView/2),this.bottom=-this.top,this.right=this.top*this.aspectRatio,this.left=-this.right,this.matrix=[2*this.near/(this.right-this.left),0,0,0,0,2*this.near/(this.top-this.bottom),0,0,0,0,-(this.far+this.near)/(this.far-this.near),-1,-this.near*(this.right+this.left)/(this.right-this.left),-this.near*(this.top+this.bottom)/(this.top-this.bottom),2*this.far*this.near/(this.near-this.far),0]}_recalculateViewMatrix(){let t=f.identity(),s=f.translate(this.position.x,this.position.y,this.position.z),i=f.rotateX(this.rotation.x),r=f.rotateY(this.rotation.y),e=f.rotateZ(this.rotation.z),h=f.multiply(t,s);h=f.multiply(h,i),h=f.multiply(h,r),h=f.multiply(h,e),this.lookAtEnabled&&(h=f.lookAt([h[12],h[13],h[14]],[this.lookAtTarget.localMatrix[12],this.lookAtTarget.localMatrix[13],this.lookAtTarget.localMatrix[14]])),this.viewMatrix=f.inverse(h)}setViewProjectionMatrix(){this._recalculateViewMatrix(),this.viewProjectionMatrix=f.multiply(this.matrix,this.viewMatrix)}setFieldOfView(t){this.fieldOfView=t*Math.PI/180,this.createMatrix()}setAspectRatio(t){this.aspectRatio=t,this.createMatrix()}setNear(t){this.near=t,this.createMatrix()}setFar(t){this.far=t,this.createMatrix()}setPosition(t,s,i){this.position={x:t,y:s,z:i}}setRotationX(t){this.rotation.x=t}setRotationY(t){this.rotation.y=t}setRotationZ(t){this.rotation.z=t}lookAt(t){this.lookAtEnabled=!0,this.lookAtTarget=t}};var J=class{constructor(){this.objects=[]}add(t){this.objects.push(t),this.objects.sort((s,i)=>{let r=s.geometryID-i.geometryID;return r||s.shader.id-i.shader.id})}};var Q=class{constructor(){this.items=[],this.position={x:0,y:0,z:0},this.rotation={x:0,y:0,z:0},this.scale={x:1,y:1,z:1},this.localMatrix=f.identity()}_recalculateModelMatrix(){let t=f.identity(),s=f.translate(this.position.x,this.position.y,this.position.z),i=f.rotateX(this.rotation.x),r=f.rotateY(this.rotation.y),e=f.rotateZ(this.rotation.z),h=f.scale(this.scale.x,this.scale.y,this.scale.z),n=f.multiply(t,s);n=f.multiply(n,i),n=f.multiply(n,r),n=f.multiply(n,e),n=f.multiply(n,h),this.localMatrix=n}setProjectionMatrix(t){this._recalculateModelMatrix(),this.projectionMatrix=t}setPosition(t,s,i){this.position={x:t,y:s,z:i},this._recalculateModelMatrix()}setRotationX(t){this.rotation.x=t,this._recalculateModelMatrix()}setRotationY(t){this.rotation.y=t,this._recalculateModelMatrix()}setRotationZ(t){this.rotation.z=t,this._recalculateModelMatrix()}setScale(t,s,i){this.scale={x:t,y:s,z:i},this._recalculateModelMatrix()}};var $=class{constructor(t,s){this.geometryID=t.id,this.geometryType=t.type,this.shader=s,this.position={x:0,y:0,z:0},this.rotation={x:0,y:0,z:0},this.scale={x:1,y:1,z:1},this.attributes=t.attributes,this.uniforms={uViewProjectionMatrix:{name:"uViewProjectionMatrix",value:null,type:"mat4"},uNormalMatrix:{name:"uNormalMatrix",value:null,type:"mat4"},uLocalMatrix:{name:"uLocalMatrix",value:null,type:"mat4"}},this.surfaceNormals=!1,this.localMatrix=f.identity(),this._setAttributeData(),this._setUniformData(),this._setDrawMode(),this._setSurfaceNormals()}_setAttributeData(){for(let t in this.attributes)this.attributes[t].location=this.shader.gl.getAttribLocation(this.shader.program,this.attributes[t].name),this.attributes[t].buffer=this.shader.gl.createBuffer(),this.shader.gl.bindBuffer(this.shader.gl.ARRAY_BUFFER,this.attributes[t].buffer),this.shader.gl.bufferData(this.shader.gl.ARRAY_BUFFER,this.attributes[t].data,this.shader.gl.STATIC_DRAW)}_setUniformData(){for(let t in this.uniforms)this.uniforms[t].location=this.shader.gl.getUniformLocation(this.shader.program,this.uniforms[t].name)}_setDrawMode(){this.geometryType?this.drawMode=this.geometryType:this.drawMode="TRIANGLES"}_setSurfaceNormals(){if(this.surfaceNormals){let t=[];for(let s=0;s=0&&this.parentCollection.items.splice(s,1)}t&&t.items.push(this),this.parentCollection=t}};var yt=0,C=class{constructor(t){this.id=yt++,this.attributes={},this.setAttribute("aPosition",new Float32Array(t),3),this._generateNormals(t)}setAttribute(t,s,i){this.attributes[t]={name:t,data:s,size:i,count:s.length/i}}_generateNormals(t){let s=[];for(var i=0;i=0?u=-.5:this.attributes.aNormal.data[a+1]==-1&&this.attributes.aNormal.data[a]<0&&(u=.5);let o=.5+Math.atan2(this.attributes.aNormal.data[a],this.attributes.aNormal.data[a+2])/(Math.PI*2),l=.5-Math.asin(this.attributes.aNormal.data[a+1])/Math.PI;r.push(o+u,1-l)}let h=6*2*s,n=3*(h/4);for(let a=0;a0?1:-1,l=Math.sqrt(a**2+u**2+o**2);e.push(a/l,u/l,o/l)}this.attributes.aNormal.data=new Float32Array(e),this.attributes.aNormal.count=e.length/3;let n=[];for(let a=0;a { 2 | var __create = Object.create; 3 | var __defProp = Object.defineProperty; 4 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 5 | var __getOwnPropNames = Object.getOwnPropertyNames; 6 | var __getProtoOf = Object.getPrototypeOf; 7 | var __hasOwnProp = Object.prototype.hasOwnProperty; 8 | var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); 9 | var __commonJS = (cb, mod) => function __require() { 10 | return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; 11 | }; 12 | var __reExport = (target, module, desc) => { 13 | if (module && typeof module === "object" || typeof module === "function") { 14 | for (let key of __getOwnPropNames(module)) 15 | if (!__hasOwnProp.call(target, key) && key !== "default") 16 | __defProp(target, key, { get: () => module[key], enumerable: !(desc = __getOwnPropDesc(module, key)) || desc.enumerable }); 17 | } 18 | return target; 19 | }; 20 | var __toModule = (module) => { 21 | return __reExport(__markAsModule(__defProp(module != null ? __create(__getProtoOf(module)) : {}, "default", module && module.__esModule && "default" in module ? { get: () => module.default, enumerable: true } : { value: module, enumerable: true })), module); 22 | }; 23 | 24 | // dist/js/sandbox.min.js 25 | var require_sandbox_min = __commonJS({ 26 | "dist/js/sandbox.min.js"(exports) { 27 | var gt = Object.defineProperty; 28 | var dt = (M) => gt(M, "__esModule", { value: true }); 29 | var bt = (M, t) => { 30 | dt(M); 31 | for (var s in t) 32 | gt(M, s, { get: t[s], enumerable: true }); 33 | }; 34 | bt(exports, { default: () => E }); 35 | var Z = class { 36 | constructor(t) { 37 | this.gl = t.getContext("webgl", { powerPreference: "high-performance" }), this.resize = this.resize.bind(this), this.render = this.render.bind(this), this.depthTest = true, this.faceCulling = true, this.pixelRatio = Math.min(window.devicePixelRatio, 2), this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true), this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, 1), this.framebuffer = null; 38 | } 39 | setPixelRatio(t) { 40 | this.pixelRatio = t; 41 | } 42 | setFrameBuffer(t) { 43 | t !== null ? this.framebuffer = t.buffer : this.framebuffer = null; 44 | } 45 | resize() { 46 | let t = this.gl.canvas.clientWidth * this.pixelRatio, s = this.gl.canvas.clientHeight * this.pixelRatio; 47 | return this.gl.canvas.width * this.pixelRatio !== t || this.gl.canvas.height * this.pixelRatio !== s ? (this.gl.canvas.width = t, this.gl.canvas.height = s, this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height), true) : false; 48 | } 49 | render(t, s) { 50 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer), this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT), this.faceCulling && this.gl.enable(this.gl.CULL_FACE), this.depthTest && this.gl.enable(this.gl.DEPTH_TEST), this.gl.enable(this.gl.BLEND), this.gl.blendEquation(this.gl.FUNC_ADD), this.gl.blendFunc(this.gl.ONE_MINUS_CONSTANT_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); 51 | let i = null, r = null; 52 | s.setViewProjectionMatrix(); 53 | for (let e of t.objects) { 54 | e.setProjectionMatrix(s.viewProjectionMatrix); 55 | let h = false; 56 | if (e.shader.program !== i && (this.gl.useProgram(e.shader.program), i = e.shader.program, h = true), h || e.attributes != r) { 57 | for (let o in e.attributes) { 58 | this.gl.enableVertexAttribArray(e.attributes[o].location), this.gl.bindBuffer(this.gl.ARRAY_BUFFER, e.attributes[o].buffer); 59 | let l = e.attributes[o].size, p = this.gl.FLOAT, m = false, x = 0, g = 0; 60 | this.gl.vertexAttribPointer(e.attributes[o].location, l, p, m, x, g); 61 | } 62 | r = e.attributes; 63 | } 64 | for (let o in e.uniforms) 65 | if (o === "uViewProjectionMatrix") 66 | this.gl.uniformMatrix4fv(e.uniforms[o].location, false, e.projectionMatrix); 67 | else if (o === "uNormalMatrix") 68 | this.gl.uniformMatrix4fv(e.uniforms[o].location, false, e.normalMatrix); 69 | else if (o === "uLocalMatrix") 70 | this.gl.uniformMatrix4fv(e.uniforms[o].location, false, e.localMatrix); 71 | else 72 | switch (e.uniforms[o].type) { 73 | case "1f": 74 | this.gl.uniform1f(e.uniforms[o].location, e.uniforms[o].value); 75 | break; 76 | case "2f": 77 | this.gl.uniform2f(e.uniforms[o].location, e.uniforms[o].value[0], e.uniforms[o].value[1]); 78 | break; 79 | case "3f": 80 | this.gl.uniform3f(e.uniforms[o].location, e.uniforms[o].value[0], e.uniforms[o].value[1], e.uniforms[o].value[2]); 81 | break; 82 | case "4f": 83 | this.gl.uniform4f(e.uniforms[o].location, e.uniforms[o].value[0], e.uniforms[o].value[1], e.uniforms[o].value[2], e.uniforms[o].value[3]); 84 | break; 85 | case "mat3": 86 | this.gl.uniformMatrix3fv(e.uniforms[o].location, false, e.uniforms[o].value); 87 | break; 88 | case "mat4": 89 | this.gl.uniformMatrix4fv(e.uniforms[o].location, false, e.uniforms[o].value); 90 | break; 91 | case "tex": 92 | this.gl.uniform1i(e.uniforms[o].location, e.uniforms[o].value.id), this.gl.activeTexture(this.gl.TEXTURE0 + e.uniforms[o].value.id), this.gl.bindTexture(this.gl.TEXTURE_2D, e.uniforms[o].value.texture); 93 | default: 94 | break; 95 | } 96 | let n = this.gl[e.drawMode], a = 0, u = e.attributes.aPosition.count; 97 | this.gl.drawArrays(n, a, u); 98 | } 99 | } 100 | }; 101 | var B = class { 102 | static cross(t, s) { 103 | return [t[1] * s[2] - t[2] * s[1], t[2] * s[0] - t[0] * s[2], t[0] * s[1] - t[1] * s[0]]; 104 | } 105 | static subtract(t, s) { 106 | return [t[0] - s[0], t[1] - s[1], t[2] - s[2]]; 107 | } 108 | static normalize(t) { 109 | let s = Math.sqrt(t[0] ** 2 + t[1] ** 2 + t[2] ** 2); 110 | return s > 1e-5 ? [t[0] / s, t[1] / s, t[2] / s] : [0, 0, 0]; 111 | } 112 | }; 113 | var f = class { 114 | static multiply(t, s) { 115 | let i = s[0 * 4 + 0], r = s[0 * 4 + 1], e = s[0 * 4 + 2], h = s[0 * 4 + 3], n = s[1 * 4 + 0], a = s[1 * 4 + 1], u = s[1 * 4 + 2], o = s[1 * 4 + 3], l = s[2 * 4 + 0], p = s[2 * 4 + 1], m = s[2 * 4 + 2], x = s[2 * 4 + 3], g = s[3 * 4 + 0], b = s[3 * 4 + 1], y = s[3 * 4 + 2], _ = s[3 * 4 + 3], P = t[0 * 4 + 0], j = t[0 * 4 + 1], N = t[0 * 4 + 2], U = t[0 * 4 + 3], d = t[1 * 4 + 0], R = t[1 * 4 + 1], T = t[1 * 4 + 2], c = t[1 * 4 + 3], A = t[2 * 4 + 0], w = t[2 * 4 + 1], I = t[2 * 4 + 2], F = t[2 * 4 + 3], v = t[3 * 4 + 0], X = t[3 * 4 + 1], k = t[3 * 4 + 2], G = t[3 * 4 + 3]; 116 | return [i * P + r * d + e * A + h * v, i * j + r * R + e * w + h * X, i * N + r * T + e * I + h * k, i * U + r * c + e * F + h * G, n * P + a * d + u * A + o * v, n * j + a * R + u * w + o * X, n * N + a * T + u * I + o * k, n * U + a * c + u * F + o * G, l * P + p * d + m * A + x * v, l * j + p * R + m * w + x * X, l * N + p * T + m * I + x * k, l * U + p * c + m * F + x * G, g * P + b * d + y * A + _ * v, g * j + b * R + y * w + _ * X, g * N + b * T + y * I + _ * k, g * U + b * c + y * F + _ * G]; 117 | } 118 | static identity() { 119 | return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 120 | } 121 | static inverse(t) { 122 | let s = new Float32Array(16), i = t[0 * 4 + 0], r = t[0 * 4 + 1], e = t[0 * 4 + 2], h = t[0 * 4 + 3], n = t[1 * 4 + 0], a = t[1 * 4 + 1], u = t[1 * 4 + 2], o = t[1 * 4 + 3], l = t[2 * 4 + 0], p = t[2 * 4 + 1], m = t[2 * 4 + 2], x = t[2 * 4 + 3], g = t[3 * 4 + 0], b = t[3 * 4 + 1], y = t[3 * 4 + 2], _ = t[3 * 4 + 3], P = m * _, j = y * x, N = u * _, U = y * o, d = u * x, R = m * o, T = e * _, c = y * h, A = e * x, w = m * h, I = e * o, F = u * h, v = l * b, X = g * p, k = n * b, G = g * a, S = n * p, q = l * a, L = i * b, z = g * r, V = i * p, O = l * r, Y = i * a, H = n * r, ft = P * a + U * p + d * b - (j * a + N * p + R * b), pt = j * r + T * p + w * b - (P * r + c * p + A * b), mt = N * r + c * a + I * b - (U * r + T * a + F * b), xt = R * r + A * a + F * p - (d * r + w * a + I * p), D = 1 / (i * ft + n * pt + l * mt + g * xt); 123 | return s[0] = D * ft, s[1] = D * pt, s[2] = D * mt, s[3] = D * xt, s[4] = D * (j * n + N * l + R * g - (P * n + U * l + d * g)), s[5] = D * (P * i + c * l + A * g - (j * i + T * l + w * g)), s[6] = D * (U * i + T * n + F * g - (N * i + c * n + I * g)), s[7] = D * (d * i + w * n + I * l - (R * i + A * n + F * l)), s[8] = D * (v * o + G * x + S * _ - (X * o + k * x + q * _)), s[9] = D * (X * h + L * x + O * _ - (v * h + z * x + V * _)), s[10] = D * (k * h + z * o + Y * _ - (G * h + L * o + H * _)), s[11] = D * (q * h + V * o + H * x - (S * h + O * o + Y * x)), s[12] = D * (k * m + q * y + X * u - (S * y + v * u + G * m)), s[13] = D * (V * y + v * e + z * m - (L * m + O * y + X * e)), s[14] = D * (L * u + H * y + G * e - (Y * y + k * e + z * u)), s[15] = D * (Y * m + S * e + O * u - (V * u + H * m + q * e)), s; 124 | } 125 | static transpose(t) { 126 | return [t[0], t[4], t[8], t[12], t[1], t[5], t[9], t[13], t[2], t[6], t[10], t[14], t[3], t[7], t[11], t[15]]; 127 | } 128 | static translate(t, s, i) { 129 | return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, t, s, i, 1]; 130 | } 131 | static rotateX(t) { 132 | let s = t * Math.PI / 180, i = Math.cos(s), r = Math.sin(s); 133 | return [1, 0, 0, 0, 0, i, r, 0, 0, -r, i, 0, 0, 0, 0, 1]; 134 | } 135 | static rotateY(t) { 136 | let s = t * Math.PI / 180, i = Math.cos(s), r = Math.sin(s); 137 | return [i, 0, -r, 0, 0, 1, 0, 0, r, 0, i, 0, 0, 0, 0, 1]; 138 | } 139 | static rotateZ(t) { 140 | let s = t * Math.PI / 180, i = Math.cos(s), r = Math.sin(s); 141 | return [i, r, 0, 0, -r, i, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 142 | } 143 | static scale(t, s, i) { 144 | return [t, 0, 0, 0, 0, s, 0, 0, 0, 0, i, 0, 0, 0, 0, 1]; 145 | } 146 | static lookAt(t, s) { 147 | let i = B.normalize(B.subtract(t, s)), r = B.normalize(B.cross([0, 1, 0], i)), e = B.normalize(B.cross(i, r)); 148 | return [r[0], r[1], r[2], 0, e[0], e[1], e[2], 0, i[0], i[1], i[2], 0, t[0], t[1], t[2], 1]; 149 | } 150 | }; 151 | var W = class { 152 | constructor(t, s, i, r, e, h) { 153 | this.left = t, this.right = s, this.bottom = i, this.top = r, this.near = e, this.far = h, this.type = "orthographic", this.position = { x: 0, y: 0, z: 0 }, this.rotation = { x: 0, y: 0, z: 0 }, this.viewMatrix = f.identity(), this.createMatrix(); 154 | } 155 | createMatrix() { 156 | this.matrix = [2 / (this.right - this.left), 0, 0, 0, 0, 2 / (this.top - this.bottom), 0, 0, 0, 0, -2 / (this.far - this.near), 0, -(this.right + this.left) / (this.right - this.left), -(this.top + this.bottom) / (this.top - this.bottom), -(this.far + this.near) / (this.far - this.near), 1]; 157 | } 158 | _recalculateViewMatrix() { 159 | let t = f.identity(), s = f.translate(this.position.x, this.position.y, this.position.z), i = f.rotateX(this.rotation.x), r = f.rotateY(this.rotation.y), e = f.rotateZ(this.rotation.z), h = f.multiply(t, s); 160 | h = f.multiply(h, i), h = f.multiply(h, r), h = f.multiply(h, e), this.lookAtEnabled && (h = f.lookAt([h[12], h[13], h[14]], [this.lookAtTarget.localMatrix[12], this.lookAtTarget.localMatrix[13], this.lookAtTarget.localMatrix[14]])), this.viewMatrix = f.inverse(h); 161 | } 162 | setViewProjectionMatrix() { 163 | this._recalculateViewMatrix(), this.viewProjectionMatrix = f.multiply(this.matrix, this.viewMatrix); 164 | } 165 | setLeft(t) { 166 | this.left = t, this.createMatrix(); 167 | } 168 | setRight(t) { 169 | this.right = t, this.createMatrix(); 170 | } 171 | setBottom(t) { 172 | this.bottom = t, this.createMatrix(); 173 | } 174 | setTop(t) { 175 | this.top = t, this.createMatrix(); 176 | } 177 | setNear(t) { 178 | this.near = t, this.createMatrix(); 179 | } 180 | setFar(t) { 181 | this.far = t, this.createMatrix(); 182 | } 183 | setPosition(t, s, i) { 184 | this.position = { x: t, y: s, z: i }; 185 | } 186 | setRotationX(t) { 187 | this.rotation.x = t; 188 | } 189 | setRotationY(t) { 190 | this.rotation.y = t; 191 | } 192 | setRotationZ(t) { 193 | this.rotation.z = t; 194 | } 195 | lookAt(t) { 196 | this.lookAtEnabled = true, this.lookAtTarget = t; 197 | } 198 | }; 199 | var K = class { 200 | constructor(t, s, i, r) { 201 | this.fieldOfView = t * Math.PI / 180, this.aspectRatio = s, this.near = i, this.far = r, this.type = "perspective", this.position = { x: 0, y: 0, z: 0 }, this.rotation = { x: 0, y: 0, z: 0 }, this.viewMatrix = f.identity(), this.lookAtEnabled = false, this.createMatrix(); 202 | } 203 | createMatrix() { 204 | this.top = this.near * Math.tan(this.fieldOfView / 2), this.bottom = -this.top, this.right = this.top * this.aspectRatio, this.left = -this.right, this.matrix = [2 * this.near / (this.right - this.left), 0, 0, 0, 0, 2 * this.near / (this.top - this.bottom), 0, 0, 0, 0, -(this.far + this.near) / (this.far - this.near), -1, -this.near * (this.right + this.left) / (this.right - this.left), -this.near * (this.top + this.bottom) / (this.top - this.bottom), 2 * this.far * this.near / (this.near - this.far), 0]; 205 | } 206 | _recalculateViewMatrix() { 207 | let t = f.identity(), s = f.translate(this.position.x, this.position.y, this.position.z), i = f.rotateX(this.rotation.x), r = f.rotateY(this.rotation.y), e = f.rotateZ(this.rotation.z), h = f.multiply(t, s); 208 | h = f.multiply(h, i), h = f.multiply(h, r), h = f.multiply(h, e), this.lookAtEnabled && (h = f.lookAt([h[12], h[13], h[14]], [this.lookAtTarget.localMatrix[12], this.lookAtTarget.localMatrix[13], this.lookAtTarget.localMatrix[14]])), this.viewMatrix = f.inverse(h); 209 | } 210 | setViewProjectionMatrix() { 211 | this._recalculateViewMatrix(), this.viewProjectionMatrix = f.multiply(this.matrix, this.viewMatrix); 212 | } 213 | setFieldOfView(t) { 214 | this.fieldOfView = t * Math.PI / 180, this.createMatrix(); 215 | } 216 | setAspectRatio(t) { 217 | this.aspectRatio = t, this.createMatrix(); 218 | } 219 | setNear(t) { 220 | this.near = t, this.createMatrix(); 221 | } 222 | setFar(t) { 223 | this.far = t, this.createMatrix(); 224 | } 225 | setPosition(t, s, i) { 226 | this.position = { x: t, y: s, z: i }; 227 | } 228 | setRotationX(t) { 229 | this.rotation.x = t; 230 | } 231 | setRotationY(t) { 232 | this.rotation.y = t; 233 | } 234 | setRotationZ(t) { 235 | this.rotation.z = t; 236 | } 237 | lookAt(t) { 238 | this.lookAtEnabled = true, this.lookAtTarget = t; 239 | } 240 | }; 241 | var J = class { 242 | constructor() { 243 | this.objects = []; 244 | } 245 | add(t) { 246 | this.objects.push(t), this.objects.sort((s, i) => { 247 | let r = s.geometryID - i.geometryID; 248 | return r || s.shader.id - i.shader.id; 249 | }); 250 | } 251 | }; 252 | var Q = class { 253 | constructor() { 254 | this.items = [], this.position = { x: 0, y: 0, z: 0 }, this.rotation = { x: 0, y: 0, z: 0 }, this.scale = { x: 1, y: 1, z: 1 }, this.localMatrix = f.identity(); 255 | } 256 | _recalculateModelMatrix() { 257 | let t = f.identity(), s = f.translate(this.position.x, this.position.y, this.position.z), i = f.rotateX(this.rotation.x), r = f.rotateY(this.rotation.y), e = f.rotateZ(this.rotation.z), h = f.scale(this.scale.x, this.scale.y, this.scale.z), n = f.multiply(t, s); 258 | n = f.multiply(n, i), n = f.multiply(n, r), n = f.multiply(n, e), n = f.multiply(n, h), this.localMatrix = n; 259 | } 260 | setProjectionMatrix(t) { 261 | this._recalculateModelMatrix(), this.projectionMatrix = t; 262 | } 263 | setPosition(t, s, i) { 264 | this.position = { x: t, y: s, z: i }, this._recalculateModelMatrix(); 265 | } 266 | setRotationX(t) { 267 | this.rotation.x = t, this._recalculateModelMatrix(); 268 | } 269 | setRotationY(t) { 270 | this.rotation.y = t, this._recalculateModelMatrix(); 271 | } 272 | setRotationZ(t) { 273 | this.rotation.z = t, this._recalculateModelMatrix(); 274 | } 275 | setScale(t, s, i) { 276 | this.scale = { x: t, y: s, z: i }, this._recalculateModelMatrix(); 277 | } 278 | }; 279 | var $ = class { 280 | constructor(t, s) { 281 | this.geometryID = t.id, this.geometryType = t.type, this.shader = s, this.position = { x: 0, y: 0, z: 0 }, this.rotation = { x: 0, y: 0, z: 0 }, this.scale = { x: 1, y: 1, z: 1 }, this.attributes = t.attributes, this.uniforms = { uViewProjectionMatrix: { name: "uViewProjectionMatrix", value: null, type: "mat4" }, uNormalMatrix: { name: "uNormalMatrix", value: null, type: "mat4" }, uLocalMatrix: { name: "uLocalMatrix", value: null, type: "mat4" } }, this.surfaceNormals = false, this.localMatrix = f.identity(), this._setAttributeData(), this._setUniformData(), this._setDrawMode(), this._setSurfaceNormals(); 282 | } 283 | _setAttributeData() { 284 | for (let t in this.attributes) 285 | this.attributes[t].location = this.shader.gl.getAttribLocation(this.shader.program, this.attributes[t].name), this.attributes[t].buffer = this.shader.gl.createBuffer(), this.shader.gl.bindBuffer(this.shader.gl.ARRAY_BUFFER, this.attributes[t].buffer), this.shader.gl.bufferData(this.shader.gl.ARRAY_BUFFER, this.attributes[t].data, this.shader.gl.STATIC_DRAW); 286 | } 287 | _setUniformData() { 288 | for (let t in this.uniforms) 289 | this.uniforms[t].location = this.shader.gl.getUniformLocation(this.shader.program, this.uniforms[t].name); 290 | } 291 | _setDrawMode() { 292 | this.geometryType ? this.drawMode = this.geometryType : this.drawMode = "TRIANGLES"; 293 | } 294 | _setSurfaceNormals() { 295 | if (this.surfaceNormals) { 296 | let t = []; 297 | for (let s = 0; s < this.geometry.attributes.aNormal.data.length; s += 9) { 298 | let i = [this.geometry.attributes.aNormal.data[s], this.geometry.attributes.aNormal.data[s + 1], this.geometry.attributes.aNormal.data[s + 2]], r = [this.geometry.attributes.aNormal.data[s + 3], this.geometry.attributes.aNormal.data[s + 4], this.geometry.attributes.aNormal.data[s + 5]], e = [this.geometry.attributes.aNormal.data[s + 6], this.geometry.attributes.aNormal.data[s + 7], this.geometry.attributes.aNormal.data[s + 8]], h = B.subtract(r, i), n = B.subtract(e, i), a = h[1] * n[2] - h[2] * n[1], u = h[2] * n[0] - h[0] * n[2], o = h[0] * n[1] - h[1] * n[0], l = B.normalize([a, u, o]); 299 | t.push(l[0], l[1], l[2]), t.push(l[0], l[1], l[2]), t.push(l[0], l[1], l[2]); 300 | } 301 | this.shader.gl.bindBuffer(this.shader.gl.ARRAY_BUFFER, this.geometry.attributes.aNormal.buffer), this.shader.gl.bufferData(this.shader.gl.ARRAY_BUFFER, new Float32Array(t), this.shader.gl.STATIC_DRAW); 302 | } 303 | } 304 | _recalculateModelMatrix() { 305 | let t = f.identity(), s = f.translate(this.position.x, this.position.y, this.position.z), i = f.rotateX(this.rotation.x), r = f.rotateY(this.rotation.y), e = f.rotateZ(this.rotation.z), h = f.scale(this.scale.x, this.scale.y, this.scale.z), n = f.multiply(t, s); 306 | n = f.multiply(n, i), n = f.multiply(n, r), n = f.multiply(n, e), n = f.multiply(n, h), this.parentCollection ? this.localMatrix = f.multiply(this.parentCollection.localMatrix, n) : this.localMatrix = n; 307 | } 308 | _recalculateNormalMatrix() { 309 | this.normalMatrix = f.transpose(f.inverse(this.localMatrix)); 310 | } 311 | setProjectionMatrix(t) { 312 | this._recalculateModelMatrix(), this._recalculateNormalMatrix(), this.projectionMatrix = t; 313 | } 314 | setPosition(t, s, i) { 315 | this.position = { x: t, y: s, z: i }, this._recalculateModelMatrix(); 316 | } 317 | setRotationX(t) { 318 | this.rotation.x = t, this._recalculateModelMatrix(); 319 | } 320 | setRotationY(t) { 321 | this.rotation.y = t, this._recalculateModelMatrix(); 322 | } 323 | setRotationZ(t) { 324 | this.rotation.z = t, this._recalculateModelMatrix(); 325 | } 326 | setScale(t, s, i) { 327 | this.scale = { x: t, y: s, z: i }, this._recalculateModelMatrix(); 328 | } 329 | setAttribute(t, s, i) { 330 | this.attributes[t] = { name: t, data: s, size: i, count: s.length / i }, this._setAttributeData(); 331 | } 332 | setUniform(t, s, i) { 333 | this.uniforms[t] = { name: t, value: s, type: i }, this._setUniformData(); 334 | } 335 | setShader(t) { 336 | this.shader = t, this._setAttributeData(), this._setUniformData(); 337 | } 338 | setParent(t) { 339 | if (this.parentCollection) { 340 | let s = this.parentCollection.items.indexOf(this); 341 | s >= 0 && this.parentCollection.items.splice(s, 1); 342 | } 343 | t && t.items.push(this), this.parentCollection = t; 344 | } 345 | }; 346 | var yt = 0; 347 | var C = class { 348 | constructor(t) { 349 | this.id = yt++, this.attributes = {}, this.setAttribute("aPosition", new Float32Array(t), 3), this._generateNormals(t); 350 | } 351 | setAttribute(t, s, i) { 352 | this.attributes[t] = { name: t, data: s, size: i, count: s.length / i }; 353 | } 354 | _generateNormals(t) { 355 | let s = []; 356 | for (var i = 0; i < t.length; i += 3) { 357 | let r = t[i], e = t[i + 1], h = t[i + 2], n = Math.sqrt(r ** 2 + e ** 2 + h ** 2); 358 | s.push(r / n, e / n, h / n); 359 | } 360 | this.setAttribute("aNormal", new Float32Array(s), 3); 361 | } 362 | }; 363 | var tt = class extends C { 364 | constructor(t, s, i, r) { 365 | let e = [], h = t / i, n = s / r; 366 | for (let l = 0; l < r; l++) 367 | for (let p = 0; p < i; p++) { 368 | let m = p * h - t / 2, x = l * n - s / 2, g = 0, b = (p + 1) * h - t / 2, y = x, _ = m, P = (l + 1) * n - s / 2, j = m, N = P, U = b, d = y, R = b, T = P; 369 | e.push(m, x, g, b, y, g, _, P, g, j, N, g, U, d, g, R, T, g); 370 | } 371 | super(e); 372 | let a = []; 373 | for (var u = 0; u < e.length; u += 3) { 374 | let l = e[u], p = e[u + 1], m = 1, x = Math.sqrt(l ** 2 + p ** 2 + m ** 2); 375 | a.push(l / x, p / x, 1); 376 | } 377 | this.attributes.aNormal.data = new Float32Array(a), this.attributes.aNormal.count = a.length / 3; 378 | let o = []; 379 | for (let l = 0; l < e.length; l += 3) { 380 | let p = (e[l] + t / 2) / t, m = (e[l + 1] + s / 2) / s; 381 | o.push(p, m); 382 | } 383 | this.setAttribute("aUV", new Float32Array(o), 2); 384 | } 385 | }; 386 | var st = class extends C { 387 | constructor(t, s) { 388 | let i = []; 389 | i.push(0, 0, 0); 390 | for (let n = 0; n < s; n++) { 391 | let a = Math.cos(n * Math.PI / (s / 2)) * t, u = Math.sin(n * Math.PI / (s / 2)) * t, o = 0; 392 | i.push(a, u, o); 393 | } 394 | i.push(Math.cos(0) * t, Math.sin(0) * t, 0); 395 | super(i); 396 | let r = []; 397 | for (var e = 0; e < i.length; e += 3) { 398 | let n = i[e], a = i[e + 1], u = 1, o = Math.sqrt(n ** 2 + a ** 2 + u ** 2); 399 | r.push(n / o, a / o, u / o); 400 | } 401 | this.attributes.aNormal.data = new Float32Array(r), this.attributes.aNormal.count = r.length / 3; 402 | let h = []; 403 | for (let n = 0; n < i.length; n += 3) { 404 | let a = (i[n] + t) / (t * 2), u = (i[n + 1] + t) / (t * 2); 405 | h.push(a, u); 406 | } 407 | this.setAttribute("aUV", new Float32Array(h), 2), this.type = "TRIANGLE_FAN"; 408 | } 409 | }; 410 | var it = class extends C { 411 | constructor(t) { 412 | let s = [], i = Math.sqrt(3) / 2 * t; 413 | s.push(-t / 2, -(t * Math.sqrt(3)) / 6, t * Math.sqrt(3) / 6, t / 2, -(t * Math.sqrt(3)) / 6, t * Math.sqrt(3) / 6, 0, t * Math.sqrt(3) / 3, 0, t / 2, -(t * Math.sqrt(3)) / 6, t * Math.sqrt(3) / 6, 0, -(t * Math.sqrt(3)) / 6, -(t * Math.sqrt(3)) / 3, 0, t * Math.sqrt(3) / 3, 0, 0, -(t * Math.sqrt(3)) / 6, -(t * Math.sqrt(3)) / 3, -t / 2, -(t * Math.sqrt(3)) / 6, t * Math.sqrt(3) / 6, 0, t * Math.sqrt(3) / 3, 0, -t / 2, -(t * Math.sqrt(3)) / 6, t * Math.sqrt(3) / 6, 0, -(t * Math.sqrt(3)) / 6, -(t * Math.sqrt(3)) / 3, t / 2, -(t * Math.sqrt(3)) / 6, t * Math.sqrt(3) / 6); 414 | super(s); 415 | let r = []; 416 | for (let e = 0; e < s.length; e += 9) 417 | e === 27 ? r.push(1, (1 - Math.sqrt(0.75)) / 2, 0.5, (1 - Math.sqrt(0.75)) / 2 + Math.sqrt(0.75), 0, (1 - Math.sqrt(0.75)) / 2) : r.push(0, (1 - Math.sqrt(0.75)) / 2, 1, (1 - Math.sqrt(0.75)) / 2, 0.5, (1 - Math.sqrt(0.75)) / 2 + Math.sqrt(0.75)); 418 | this.setAttribute("aUV", new Float32Array(r), 2); 419 | } 420 | }; 421 | var et = class extends C { 422 | constructor(t, s, i, r, e, h) { 423 | let n = [], a = []; 424 | u("x", "y", "z", t, s, i, r, e, "front", false, false), u("x", "y", "z", t, s, -i, r, e, "back", true, false), u("x", "z", "y", t, i, s, r, h, "back", false, true), u("x", "z", "y", t, i, -s, r, h, "front", false, false), u("z", "y", "x", i, s, t, h, e, "back", true, false), u("z", "y", "x", i, s, -t, h, e, "front", false, false); 425 | function u(o, l, p, m, x, g, b, y, _, P, j) { 426 | let N = m / b, U = x / y, d = g / 2; 427 | for (let R = 0; R < y; R++) 428 | for (let T = 0; T < b; T++) { 429 | let c = {}; 430 | c[o] = [], c[l] = [], c[p] = []; 431 | let A = T * N - m / 2, w = R * U - x / 2, I = (T + 1) * N - m / 2, F = (R + 1) * U - x / 2; 432 | _ === "front" ? (c[o].push(A), c[l].push(w), c[p].push(d), c[o].push(I), c[l].push(w), c[p].push(d), c[o].push(A), c[l].push(F), c[p].push(d), c[o].push(A), c[l].push(F), c[p].push(d), c[o].push(I), c[l].push(w), c[p].push(d), c[o].push(I), c[l].push(F), c[p].push(d)) : _ === "back" && (c[o].push(I), c[l].push(w), c[p].push(d), c[o].push(A), c[l].push(w), c[p].push(d), c[o].push(I), c[l].push(F), c[p].push(d), c[o].push(I), c[l].push(F), c[p].push(d), c[o].push(A), c[l].push(w), c[p].push(d), c[o].push(A), c[l].push(F), c[p].push(d)), n.push(c.x[0], c.y[0], c.z[0], c.x[1], c.y[1], c.z[1], c.x[2], c.y[2], c.z[2], c.x[3], c.y[3], c.z[3], c.x[4], c.y[4], c.z[4], c.x[5], c.y[5], c.z[5]); 433 | for (let v = 0; v < 6; v++) { 434 | let X, k; 435 | P ? X = 1 - (c[o][v] + m / 2) / m : X = (c[o][v] + m / 2) / m, j ? k = 1 - (c[l][v] + x / 2) / x : k = (c[l][v] + x / 2) / x, a.push(X, k); 436 | } 437 | } 438 | } 439 | super(n); 440 | this.setAttribute("aUV", new Float32Array(a), 2); 441 | } 442 | }; 443 | var rt = class extends C { 444 | constructor(t, s) { 445 | let i = [], r = [], e = Math.PI * 2 / s; 446 | for (let a = 0; a < s; a++) 447 | for (let u = 0; u < s; u++) { 448 | let o = t * Math.cos(u * e) * Math.sin(a * e), l = t * Math.cos(a * e), p = t * Math.sin(u * e) * Math.sin(a * e), m = t * Math.cos(u * e) * Math.sin((a + 1) * e), x = t * Math.cos((a + 1) * e), g = t * Math.sin(u * e) * Math.sin((a + 1) * e), b = t * Math.cos((u + 1) * e) * Math.sin((a + 1) * e), y = t * Math.cos((a + 1) * e), _ = t * Math.sin((u + 1) * e) * Math.sin((a + 1) * e), P = o, j = l, N = p, U = b, d = y, R = _, T = t * Math.cos((u + 1) * e) * Math.sin(a * e), c = t * Math.cos(a * e), A = t * Math.sin((u + 1) * e) * Math.sin(a * e); 449 | i.push(o, l, p, m, x, g, b, y, _, P, j, N, U, d, R, T, c, A); 450 | } 451 | super(i); 452 | for (let a = 0; a < this.attributes.aNormal.data.length; a += 3) { 453 | let u = 0; 454 | this.attributes.aNormal.data[a + 1] == -1 && this.attributes.aNormal.data[a] >= 0 ? u = -0.5 : this.attributes.aNormal.data[a + 1] == -1 && this.attributes.aNormal.data[a] < 0 && (u = 0.5); 455 | let o = 0.5 + Math.atan2(this.attributes.aNormal.data[a], this.attributes.aNormal.data[a + 2]) / (Math.PI * 2), l = 0.5 - Math.asin(this.attributes.aNormal.data[a + 1]) / Math.PI; 456 | r.push(o + u, 1 - l); 457 | } 458 | let h = 6 * 2 * s, n = 3 * (h / 4); 459 | for (let a = 0; a < r.length; a += h) 460 | a !== 0 && (r[a - n] = 1, r[a - (n - 2)] = 1, r[a - (n - 6)] = 1); 461 | r[r.length - n] = 1, r[r.length - (n - 2)] = 1, r[r.length - (n - 6)] = 1, this.setAttribute("aUV", new Float32Array(r), 2); 462 | } 463 | }; 464 | var ot = class extends C { 465 | constructor(t, s, i) { 466 | let r = []; 467 | for (let a = 0; a < i; a++) { 468 | r.push(0, 0, s / 2); 469 | let u = Math.cos(a * Math.PI / (i / 2)) * t, o = Math.sin(a * Math.PI / (i / 2)) * t, l = s / 2; 470 | r.push(u, o, l); 471 | let p = Math.cos((a + 1) * Math.PI / (i / 2)) * t, m = Math.sin((a + 1) * Math.PI / (i / 2)) * t, x = s / 2; 472 | r.push(p, m, x); 473 | } 474 | for (let a = 0; a < i; a++) { 475 | r.push(0, 0, -s / 2); 476 | let u = Math.cos(a * Math.PI / (i / 2)) * t, o = Math.sin(a * Math.PI / (i / 2)) * t, l = -s / 2; 477 | r.push(u, o, l); 478 | let p = Math.cos((a + 1) * Math.PI / (i / 2)) * t, m = Math.sin((a + 1) * Math.PI / (i / 2)) * t, x = -s / 2; 479 | r.push(p, m, x); 480 | } 481 | for (let a = 0; a < i; a++) { 482 | let u = Math.cos(a * Math.PI / (i / 2)) * t, o = Math.sin(a * Math.PI / (i / 2)) * t, l = s / 2; 483 | r.push(u, o, l); 484 | let p = Math.cos((a + 1) * Math.PI / (i / 2)) * t, m = Math.sin((a + 1) * Math.PI / (i / 2)) * t; 485 | r.push(p, m, l); 486 | let x = -s / 2; 487 | r.push(u, o, x), r.push(p, m, x), r.push(u, o, x), r.push(p, m, l); 488 | } 489 | super(r); 490 | let e = []; 491 | for (var h = 0; h < r.length; h += 3) { 492 | let a = r[h], u = r[h + 1], o = r[h + 2] > 0 ? 1 : -1, l = Math.sqrt(a ** 2 + u ** 2 + o ** 2); 493 | e.push(a / l, u / l, o / l); 494 | } 495 | this.attributes.aNormal.data = new Float32Array(e), this.attributes.aNormal.count = e.length / 3; 496 | let n = []; 497 | for (let a = 0; a < r.length; a += 3) { 498 | let u = (r[a] + t) / (t * 2), o = (r[a + 1] + t) / (t * 2); 499 | n.push(u, o); 500 | } 501 | this.setAttribute("aUV", new Float32Array(n), 2); 502 | } 503 | }; 504 | var _t = 0; 505 | var at = class { 506 | constructor(t, s, i) { 507 | let r = this._createShader(t, t.VERTEX_SHADER, s), e = this._createShader(t, t.FRAGMENT_SHADER, i); 508 | this.gl = t, this.id = _t++, this.program = this._createProgram(t, r, e); 509 | } 510 | _createShader(t, s, i) { 511 | let r = t.createShader(s); 512 | if (t.shaderSource(r, i), t.compileShader(r), t.getShaderParameter(r, t.COMPILE_STATUS)) 513 | return r; 514 | console.log(t.getShaderInfoLog(r)), t.deleteShader(r); 515 | } 516 | _createProgram(t, s, i) { 517 | let r = t.createProgram(); 518 | if (t.attachShader(r, s), t.attachShader(r, i), t.linkProgram(r), t.getProgramParameter(r, t.LINK_STATUS)) 519 | return r; 520 | console.log(t.getProgramInfoLog(r)), t.deleteProgram(r); 521 | } 522 | }; 523 | var Mt = 0; 524 | var ct = class { 525 | constructor(t, s) { 526 | this.gl = t, this.texture = this.gl.createTexture(), this.id = Mt++, this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture), this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, 1, 1, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255])), this.image = new Image(), this.image.addEventListener("load", this.attachImage.bind(this)), this.image.src = s; 527 | } 528 | attachImage() { 529 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture), this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.image), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); 530 | } 531 | }; 532 | var ut = class { 533 | constructor(t, s, i, r, e, h) { 534 | switch (this.gl = t, this.texture = this.gl.createTexture(), this.id = Mt++, this.width = i, this.height = r, this.data = e ? new Uint8Array(e) : null, s) { 535 | case "rgba": 536 | this.format = this.gl.RGBA; 537 | break; 538 | case "rgb": 539 | this.format = this.gl.RGB; 540 | break; 541 | case "luminance_alpha": 542 | this.format = this.gl.LUMINANCE_ALPHA; 543 | break; 544 | case "luminance": 545 | this.format = this.gl.LUMINANCE; 546 | break; 547 | default: 548 | this.format = this.gl.RGBA; 549 | break; 550 | } 551 | switch (h) { 552 | case "linear": 553 | this.filter = this.gl.LINEAR; 554 | break; 555 | case "nearest": 556 | this.filter = this.gl.NEAREST; 557 | break; 558 | default: 559 | this.filter = this.gl.NEAREST; 560 | break; 561 | } 562 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture), this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.format, i, r, 0, this.format, this.gl.UNSIGNED_BYTE, this.data), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.filter), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.filter); 563 | } 564 | }; 565 | var ht = class { 566 | constructor(t, s) { 567 | this.type = t, this.type === "directional" ? this.direction = B.normalize([s[0], s[1], s[2]]) : this.type === "point" && (this.position = [s[0], s[1], s[2]]); 568 | } 569 | setDirection(t, s, i) { 570 | this.direction = B.normalize([t, s, i]); 571 | } 572 | setPosition(t, s, i) { 573 | this.position = [t, s, i]; 574 | } 575 | }; 576 | var nt = class { 577 | constructor(t, s) { 578 | this.gl = t, this.target = s, this.buffer = this.gl.createFramebuffer(), this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffer), this.depthBuffer = this.gl.createRenderbuffer(), this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.depthBuffer), this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, this.target.width, this.target.height), this.attachTexture(this.target), this.attachRenderBuffer(); 579 | } 580 | resize(t, s) { 581 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.target.texture), this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.target.format, t, s, 0, this.target.format, this.gl.UNSIGNED_BYTE, null), this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.depthBuffer), this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, t, s); 582 | } 583 | attachTexture(t) { 584 | this.target = t, this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, this.target.texture, 0); 585 | } 586 | attachRenderBuffer() { 587 | this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, this.depthBuffer); 588 | } 589 | }; 590 | var lt = class { 591 | constructor(t, s, i) { 592 | this.gl = t, this.mouse = s, this.camera = i, this.color = new Uint8Array(4), this.selectedIndex = -1, this.objectCount = 0, this.viewport = { width: window.innerWidth, height: window.innerHeight, aspectRatio: window.innerWidth / window.innerHeight }; 593 | } 594 | resize(t) { 595 | this.viewport.width = t.width, this.viewport.height = t.height, this.viewport.aspectRatio = t.width / t.height; 596 | } 597 | _getPixel() { 598 | this.pixel = { x: this.mouse.x * this.viewport.width / this.viewport.width, y: this.viewport.height - this.mouse.y * this.viewport.height / this.viewport.height - 1 }; 599 | } 600 | _getColor() { 601 | this.gl.readPixels(0, 0, 1, 1, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.color); 602 | } 603 | getMatrix() { 604 | return this._getPixel(), this.camera.type === "perspective" ? (this.top = this.camera.near * Math.tan(this.camera.fieldOfView / 2), this.bottom = -this.top, this.right = this.top * this.camera.aspectRatio, this.left = -this.right) : this.camera.type === "orthographic" && (this.top = this.camera.top, this.bottom = this.camera.bottom, this.right = this.camera.right, this.left = this.camera.left), this.width = Math.abs(this.right - this.left), this.height = Math.abs(this.top - this.bottom), this.pixelLeft = this.left + this.pixel.x * this.width / this.viewport.width, this.pixelRight = this.pixelLeft + 1 / this.viewport.width, this.pixelTop = this.bottom + this.pixel.y * this.height / this.viewport.height, this.pixelBottom = this.pixelTop + 1 / this.viewport.height, this.near = this.camera.near, this.far = this.camera.far, this.camera.type === "perspective" ? this.matrix = [2 * this.near / (this.pixelRight - this.pixelLeft), 0, 0, 0, 0, 2 * this.near / (this.pixelTop - this.pixelBottom), 0, 0, (this.pixelRight + this.pixelLeft) / (this.pixelRight - this.pixelLeft), (this.pixelTop + this.pixelBottom) / (this.pixelTop - this.pixelBottom), -(this.far + this.near) / (this.far - this.near), -1, 0, 0, 2 * this.far * this.near / (this.near - this.far), 0] : this.camera.type === "orthographic" && (this.matrix = [2 / (this.pixelRight - this.pixelLeft), 0, 0, 0, 0, 2 / (this.pixelTop - this.pixelBottom), 0, 0, 0, 0, -2 / (this.far - this.near), 0, -((this.pixelRight + this.pixelLeft) / (this.pixelRight - this.pixelLeft)), -((this.pixelTop + this.pixelBottom) / (this.pixelTop - this.pixelBottom)), -((this.far + this.near) / (this.far - this.near)), 1]), this.matrix; 605 | } 606 | getObjectIndex() { 607 | return this._getColor(), this.selectedIndex = this.color[3] / 255 * this.objectCount - 1, this.selectedIndex; 608 | } 609 | }; 610 | var E = class { 611 | static createColor(t, s, i) { 612 | return { r: t / 255, g: s / 255, b: i / 255 }; 613 | } 614 | }; 615 | E.Renderer = Z; 616 | E.Orthographic = W; 617 | E.Perspective = K; 618 | E.Volume = J; 619 | E.Collection = Q; 620 | E.Mesh = $; 621 | E.Geometry = C; 622 | E.Plane = tt; 623 | E.Circle = st; 624 | E.Tetrahedron = it; 625 | E.Cube = et; 626 | E.Sphere = rt; 627 | E.Cylinder = ot; 628 | E.Program = at; 629 | E.ImageTexture = ct; 630 | E.DataTexture = ut; 631 | E.Light = ht; 632 | E.FrameBuffer = nt; 633 | E.ColorPicker = lt; 634 | } 635 | }); 636 | 637 | // src/js/demo/main.js 638 | var import_sandbox_min = __toModule(require_sandbox_min()); 639 | 640 | // src/shaders/primitive/vertex.glsl 641 | var vertex_default = "attribute vec4 aPosition;\nattribute vec3 aNormal;\nattribute vec2 aUV;\n\nuniform mat4 uViewProjectionMatrix;\nuniform mat4 uNormalMatrix;\nuniform mat4 uLocalMatrix;\n\nvarying vec3 vNormal;\nvarying vec2 vUV;\nvarying vec3 vPos;\n\nvoid main() {\n vec4 position = uViewProjectionMatrix * uLocalMatrix * aPosition;\n gl_Position = position;\n vNormal = aNormal + 0.5;\n vUV = aUV;\n vPos = aPosition.xyz;\n}"; 642 | 643 | // src/shaders/primitive/fragment.glsl 644 | var fragment_default = "precision mediump float;\n\nvarying vec3 vNormal;\nvarying vec2 vUV;\nvarying vec3 vPos;\n\nvoid main() {\n float distance = 0.25 + (1.0 - 0.25) * (vPos.z - (-1.0)) / (1.0 - (-1.0));\n vec3 green = vec3(0.420, 0.831, 0.565) * distance;\n\n gl_FragColor = vec4(green, 1.0);\n}"; 645 | 646 | // src/js/demo/main.js 647 | var now = 0; 648 | var time = 0; 649 | var then = 0; 650 | var planeCanvas = document.getElementById("primitive--plane"); 651 | var planeRenderer = new import_sandbox_min.default.Renderer(planeCanvas); 652 | planeRenderer.resize(); 653 | var planeVolume = new import_sandbox_min.default.Volume(); 654 | var planeCamera = new import_sandbox_min.default.Perspective(35, 1, 0.1, 100); 655 | planeCamera.setPosition(0, 0, 6); 656 | var planeShader = new import_sandbox_min.default.Program(planeRenderer.gl, vertex_default, fragment_default); 657 | var planeGeometry = new import_sandbox_min.default.Plane(2, 2, 1, 1); 658 | var planeMesh = new import_sandbox_min.default.Mesh(planeGeometry, planeShader); 659 | planeVolume.add(planeMesh); 660 | var circleCanvas = document.getElementById("primitive--circle"); 661 | var circleRenderer = new import_sandbox_min.default.Renderer(circleCanvas); 662 | circleRenderer.resize(); 663 | var circleVolume = new import_sandbox_min.default.Volume(); 664 | var circleCamera = new import_sandbox_min.default.Perspective(35, 1, 0.1, 100); 665 | circleCamera.setPosition(0, 0, 6); 666 | var circleShader = new import_sandbox_min.default.Program(circleRenderer.gl, vertex_default, fragment_default); 667 | var circleGeometry = new import_sandbox_min.default.Circle(1, 64); 668 | var circleMesh = new import_sandbox_min.default.Mesh(circleGeometry, circleShader); 669 | circleVolume.add(circleMesh); 670 | var tetrahedronCanvas = document.getElementById("primitive--tetrahedron"); 671 | var tetrahedronRenderer = new import_sandbox_min.default.Renderer(tetrahedronCanvas); 672 | tetrahedronRenderer.resize(); 673 | var tetrahedronVolume = new import_sandbox_min.default.Volume(); 674 | var tetrahedronCamera = new import_sandbox_min.default.Perspective(35, 1, 0.1, 100); 675 | tetrahedronCamera.setPosition(0, 0, 6); 676 | var tetrahedronShader = new import_sandbox_min.default.Program(tetrahedronRenderer.gl, vertex_default, fragment_default); 677 | var tetrahedronGeometry = new import_sandbox_min.default.Tetrahedron(2); 678 | var tetrahedronMesh = new import_sandbox_min.default.Mesh(tetrahedronGeometry, tetrahedronShader); 679 | tetrahedronVolume.add(tetrahedronMesh); 680 | var cubeCanvas = document.getElementById("primitive--cube"); 681 | var cubeRenderer = new import_sandbox_min.default.Renderer(cubeCanvas); 682 | cubeRenderer.resize(); 683 | var cubeVolume = new import_sandbox_min.default.Volume(); 684 | var cubeCamera = new import_sandbox_min.default.Perspective(35, 1, 0.1, 100); 685 | cubeCamera.setPosition(0, 0, 6); 686 | var cubeShader = new import_sandbox_min.default.Program(cubeRenderer.gl, vertex_default, fragment_default); 687 | var cubeGeometry = new import_sandbox_min.default.Cube(2, 2, 2, 1, 1, 1); 688 | var cubeMesh = new import_sandbox_min.default.Mesh(cubeGeometry, cubeShader); 689 | cubeVolume.add(cubeMesh); 690 | var sphereCanvas = document.getElementById("primitive--sphere"); 691 | var sphereRenderer = new import_sandbox_min.default.Renderer(sphereCanvas); 692 | sphereRenderer.resize(); 693 | var sphereVolume = new import_sandbox_min.default.Volume(); 694 | var sphereCamera = new import_sandbox_min.default.Perspective(35, 1, 0.1, 100); 695 | sphereCamera.setPosition(0, 0, 6); 696 | var sphereShader = new import_sandbox_min.default.Program(sphereRenderer.gl, vertex_default, fragment_default); 697 | var sphereGeometry = new import_sandbox_min.default.Sphere(1, 64); 698 | var sphereMesh = new import_sandbox_min.default.Mesh(sphereGeometry, sphereShader); 699 | sphereVolume.add(sphereMesh); 700 | var cylinderCanvas = document.getElementById("primitive--cylinder"); 701 | var cylinderRenderer = new import_sandbox_min.default.Renderer(cylinderCanvas); 702 | cylinderRenderer.resize(); 703 | var cylinderVolume = new import_sandbox_min.default.Volume(); 704 | var cylinderCamera = new import_sandbox_min.default.Perspective(35, 1, 0.1, 100); 705 | cylinderCamera.setPosition(0, 0, 6); 706 | var cylinderShader = new import_sandbox_min.default.Program(cylinderRenderer.gl, vertex_default, fragment_default); 707 | var cylinderGeometry = new import_sandbox_min.default.Cylinder(1, 2, 64); 708 | var cylinderMesh = new import_sandbox_min.default.Mesh(cylinderGeometry, cylinderShader); 709 | cylinderVolume.add(cylinderMesh); 710 | var perspectiveCanvas = document.getElementById("perspective"); 711 | var perspectiveRenderer = new import_sandbox_min.default.Renderer(perspectiveCanvas); 712 | perspectiveRenderer.resize(); 713 | var perspectiveVolume = new import_sandbox_min.default.Volume(); 714 | var perspectiveCamera = new import_sandbox_min.default.Perspective(35, 1.85, 0.1, 100); 715 | perspectiveCamera.setPosition(0, 0, 6); 716 | var perspectiveShader = new import_sandbox_min.default.Program(perspectiveRenderer.gl, vertex_default, fragment_default); 717 | var perspectiveGeometry = new import_sandbox_min.default.Cube(2, 2, 2, 1, 1, 1); 718 | var perspectiveMesh = new import_sandbox_min.default.Mesh(perspectiveGeometry, perspectiveShader); 719 | perspectiveVolume.add(perspectiveMesh); 720 | var perspectiveFOV = document.getElementById("perspective-fov"); 721 | perspectiveFOV.addEventListener("input", (event) => { 722 | perspectiveCamera.setFieldOfView(event.target.value); 723 | }); 724 | var perspectiveAspect = document.getElementById("perspective-aspect"); 725 | perspectiveAspect.addEventListener("input", (event) => { 726 | perspectiveCamera.setAspectRatio(event.target.value); 727 | }); 728 | var perspectiveNear = document.getElementById("perspective-near"); 729 | perspectiveNear.addEventListener("input", (event) => { 730 | perspectiveCamera.setNear(parseFloat(event.target.value)); 731 | }); 732 | var perspectiveFar = document.getElementById("perspective-far"); 733 | perspectiveFar.addEventListener("input", (event) => { 734 | perspectiveCamera.setFar(parseFloat(event.target.value)); 735 | }); 736 | var orthographicCanvas = document.getElementById("orthographic"); 737 | var orthographicRenderer = new import_sandbox_min.default.Renderer(orthographicCanvas); 738 | orthographicRenderer.resize(); 739 | var orthographicVolume = new import_sandbox_min.default.Volume(); 740 | var orthographicCamera = new import_sandbox_min.default.Orthographic(-4.43359375, 4.43359375, -2, 2, -2, 2); 741 | var orthographicShader = new import_sandbox_min.default.Program(orthographicRenderer.gl, vertex_default, fragment_default); 742 | var orthographicGeometry = new import_sandbox_min.default.Cube(2, 2, 2, 1, 1, 1); 743 | var orthographicMesh = new import_sandbox_min.default.Mesh(orthographicGeometry, orthographicShader); 744 | orthographicVolume.add(orthographicMesh); 745 | var orthographicLeft = document.getElementById("orthographic-left"); 746 | orthographicLeft.addEventListener("input", (event) => { 747 | orthographicCamera.setLeft(parseFloat(event.target.value)); 748 | }); 749 | var orthographicRight = document.getElementById("orthographic-right"); 750 | orthographicRight.addEventListener("input", (event) => { 751 | orthographicCamera.setRight(parseFloat(event.target.value)); 752 | }); 753 | var orthographicBottom = document.getElementById("orthographic-bottom"); 754 | orthographicBottom.addEventListener("input", (event) => { 755 | orthographicCamera.setBottom(parseFloat(event.target.value)); 756 | }); 757 | var orthographicTop = document.getElementById("orthographic-top"); 758 | orthographicTop.addEventListener("input", (event) => { 759 | orthographicCamera.setTop(parseFloat(event.target.value)); 760 | }); 761 | var orthographicNear = document.getElementById("orthographic-near"); 762 | orthographicNear.addEventListener("input", (event) => { 763 | orthographicCamera.setNear(parseFloat(event.target.value)); 764 | }); 765 | var orthographicFar = document.getElementById("orthographic-far"); 766 | orthographicFar.addEventListener("input", (event) => { 767 | orthographicCamera.setFar(parseFloat(event.target.value)); 768 | }); 769 | var update = (current) => { 770 | now = current; 771 | time += now - then; 772 | then = now; 773 | planeMesh.setRotationX(Math.sin(time / 1e3) * 30); 774 | planeMesh.setRotationY(Math.cos(time / 1200) * 30); 775 | planeRenderer.gl.clearColor(0, 0, 0, 0); 776 | planeRenderer.render(planeVolume, planeCamera); 777 | circleMesh.setRotationX(Math.sin(time / 1e3) * 30); 778 | circleMesh.setRotationY(Math.cos(time / 1200) * 30); 779 | circleRenderer.gl.clearColor(0, 0, 0, 0); 780 | circleRenderer.render(circleVolume, circleCamera); 781 | tetrahedronMesh.setRotationX(Math.sin(time / 1e3) * 30); 782 | tetrahedronMesh.setRotationY(Math.cos(time / 1200) * 30); 783 | tetrahedronRenderer.gl.clearColor(0, 0, 0, 0); 784 | tetrahedronRenderer.render(tetrahedronVolume, tetrahedronCamera); 785 | cubeMesh.setRotationX(Math.sin(time / 1e3) * 30); 786 | cubeMesh.setRotationY(Math.cos(time / 1200) * 30); 787 | cubeRenderer.gl.clearColor(0, 0, 0, 0); 788 | cubeRenderer.render(cubeVolume, cubeCamera); 789 | sphereMesh.setRotationX(Math.sin(time / 1e3) * 30); 790 | sphereMesh.setRotationY(Math.cos(time / 1200) * 30); 791 | sphereRenderer.gl.clearColor(0, 0, 0, 0); 792 | sphereRenderer.render(sphereVolume, sphereCamera); 793 | cylinderMesh.setRotationX(Math.sin(time / 1e3) * 30); 794 | cylinderMesh.setRotationY(Math.cos(time / 1200) * 30); 795 | cylinderRenderer.gl.clearColor(0, 0, 0, 0); 796 | cylinderRenderer.render(cylinderVolume, cylinderCamera); 797 | perspectiveMesh.setRotationX(Math.sin(time / 1e3) * 30); 798 | perspectiveMesh.setRotationY(Math.cos(time / 1200) * 30); 799 | perspectiveRenderer.gl.clearColor(1, 1, 1, 1); 800 | perspectiveRenderer.render(perspectiveVolume, perspectiveCamera); 801 | orthographicMesh.setRotationX(Math.sin(time / 1e3) * 30); 802 | orthographicMesh.setRotationY(Math.cos(time / 1200) * 30); 803 | orthographicRenderer.gl.clearColor(1, 1, 1, 1); 804 | orthographicRenderer.render(orthographicVolume, orthographicCamera); 805 | window.requestAnimationFrame(update); 806 | }; 807 | window.requestAnimationFrame(update); 808 | })(); 809 | --------------------------------------------------------------------------------