Comparing version 0.0.7 to 0.0.8
{ | ||
"name": "ogl", | ||
"version": "0.0.7", | ||
"version": "0.0.8", | ||
"description": "WebGL Framework", | ||
@@ -5,0 +5,0 @@ "main": "src", |
@@ -35,3 +35,3 @@ <p align="center"> | ||
Use directly in your project with es6 modules and load directly in the browser - **no dev-stack required**. | ||
[Download](https://github.com/oframe/ogl/archive/master.zip) and [load directly in the browser](https://developers.google.com/web/fundamentals/primers/modules) using es6 modules - **no dev-stack required**. | ||
@@ -38,0 +38,0 @@ **or** |
import {Transform} from './Transform.js'; | ||
import {Mat4} from '../math/Mat4.js'; | ||
import {Vec3} from '../math/Vec3.js'; | ||
// Used in unproject method | ||
const tempMat4 = new Mat4(); | ||
const tempVec3a = new Vec3(); | ||
const tempVec3b = new Vec3(); | ||
@@ -28,2 +30,3 @@ export class Camera extends Transform { | ||
this.viewMatrix = new Mat4(); | ||
this.projectionViewMatrix = new Mat4(); | ||
@@ -62,2 +65,5 @@ // Use orthographic if values set, else default to perspective camera | ||
this.viewMatrix.inverse(this.worldMatrix); | ||
// used for sorting | ||
this.projectionViewMatrix.multiply(this.projectionMatrix, this.viewMatrix); | ||
return this; | ||
@@ -84,2 +90,45 @@ } | ||
} | ||
updateFrustum() { | ||
if (!this.frustum) { | ||
this.frustum = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()]; | ||
} | ||
const m = this.projectionViewMatrix; | ||
this.frustum[0].set(m[3] - m[0], m[7] - m[4], m[11] - m[8]).constant = m[15] - m[12]; // -x | ||
this.frustum[1].set(m[3] + m[0], m[7] + m[4], m[11] + m[8]).constant = m[15] + m[12]; // +x | ||
this.frustum[2].set(m[3] + m[1], m[7] + m[5], m[11] + m[9]).constant = m[15] + m[13]; // +y | ||
this.frustum[3].set(m[3] - m[1], m[7] - m[5], m[11] - m[9]).constant = m[15] - m[13]; // -y | ||
this.frustum[4].set(m[3] - m[2], m[7] - m[6], m[11] - m[10]).constant = m[15] - m[14]; // +z (far) | ||
this.frustum[5].set(m[3] + m[2], m[7] + m[6], m[11] + m[10]).constant = m[15] + m[14]; // -z (near) | ||
for (let i = 0; i < 6; i++) { | ||
const invLen = 1.0 / this.frustum[i].length(); | ||
this.frustum[i].multiply(invLen); | ||
this.frustum[i].constant *= invLen; | ||
} | ||
} | ||
frustumIntersectsMesh(node) { | ||
if (!node.geometry.bounds || node.geometry.bounds.radius === Infinity) node.geometry.computeBoundingSphere(); | ||
const center = tempVec3a; | ||
center.copy(node.geometry.bounds.center); | ||
center.applyMatrix4(node.worldMatrix); | ||
const radius = node.geometry.bounds.radius * node.worldMatrix.getMaxScaleOnAxis(); | ||
return this.frustumIntersectsSphere(center, radius); | ||
} | ||
frustumIntersectsSphere(center, radius) { | ||
const normal = tempVec3b; | ||
for (let i = 0; i < 6; i++) { | ||
const plane = this.frustum[i]; | ||
const distance = normal.copy(plane).dot(center) + plane.constant; | ||
if (distance < -radius) return false; | ||
} | ||
return true; | ||
} | ||
} |
@@ -18,2 +18,6 @@ // attribute params | ||
import {Vec3} from '../math/Vec3.js'; | ||
const tempVec3 = new Vec3(); | ||
let ID = 0; | ||
@@ -162,2 +166,61 @@ | ||
computeBoundingBox(array) { | ||
// Use position buffer if available | ||
if (!array && this.attributes.position) array = this.attributes.position.data; | ||
if (!array) console.warn('No position buffer found to compute bounds'); | ||
if (!this.bounds) { | ||
this.bounds = { | ||
min: new Vec3(), | ||
max: new Vec3(), | ||
center: new Vec3(), | ||
scale: new Vec3(), | ||
radius: Infinity, | ||
}; | ||
} | ||
const min = this.bounds.min; | ||
const max = this.bounds.max; | ||
const center = this.bounds.center; | ||
const scale = this.bounds.scale; | ||
min.set(+Infinity); | ||
max.set(-Infinity); | ||
for (let i = 0, l = array.length; i < l; i += 3) { | ||
const x = array[i]; | ||
const y = array[i + 1]; | ||
const z = array[i + 2]; | ||
min.x = Math.min(x, min.x); | ||
min.y = Math.min(y, min.y); | ||
min.z = Math.min(z, min.z); | ||
max.x = Math.max(x, max.x); | ||
max.y = Math.max(y, max.y); | ||
max.z = Math.max(z, max.z); | ||
} | ||
scale.sub(max, min); | ||
center.add(min, max).divide(2); | ||
} | ||
computeBoundingSphere(array) { | ||
// Use position buffer if available | ||
if (!array && this.attributes.position) array = this.attributes.position.data; | ||
if (!array) console.warn('No position buffer found to compute bounds'); | ||
if (!this.bounds) this.computeBoundingBox(array); | ||
let maxRadiusSq = 0; | ||
for (let i = 0, l = array.length; i < l; i += 3) { | ||
tempVec3.fromArray(array, i); | ||
maxRadiusSq = Math.max(maxRadiusSq, this.bounds.center.squaredDistance(tempVec3)); | ||
} | ||
this.bounds.radius = Math.sqrt(maxRadiusSq); | ||
} | ||
remove() { | ||
@@ -164,0 +227,0 @@ if (this.vao) this.gl.renderer.deleteVertexArray(this.vao); |
import {Transform} from './Transform.js'; | ||
import {Mat3} from '../math/Mat3.js'; | ||
import {Mat4} from '../math/Mat4.js'; | ||
import {Vec3} from '../math/Vec3.js'; | ||
const tempVec3 = new Vec3(); | ||
let ID = 0; | ||
@@ -13,5 +12,8 @@ export class Mesh extends Transform { | ||
mode = gl.TRIANGLES, | ||
frustumCulled = true, | ||
renderOrder = 0, | ||
} = {}) { | ||
super(gl); | ||
this.gl = gl; | ||
this.id = ID++; | ||
@@ -22,2 +24,8 @@ this.geometry = geometry; | ||
// Used to skip frustum culling | ||
this.frustumCulled = frustumCulled; | ||
// Override sorting to force an order | ||
this.renderOrder = renderOrder; | ||
this.modelViewMatrix = new Mat4(); | ||
@@ -40,4 +48,4 @@ this.normalMatrix = new Mat3(); | ||
draw({ | ||
camera, | ||
} = {}) { | ||
camera, | ||
} = {}) { | ||
this.onBeforeRender && this.onBeforeRender({mesh: this, camera}); | ||
@@ -71,64 +79,2 @@ | ||
} | ||
computeBoundingBox(array) { | ||
// Use position buffer if available | ||
if (!array && this.geometry.attributes.position) array = this.geometry.attributes.position.data; | ||
if (!array) console.warn('No position buffer found to compute bounds'); | ||
if (!this.bounds) { | ||
this.bounds = { | ||
min: new Vec3(), | ||
max: new Vec3(), | ||
center: new Vec3(), | ||
scale: new Vec3(), | ||
radius: Infinity, | ||
}; | ||
} | ||
const min = this.bounds.min; | ||
const max = this.bounds.max; | ||
const center = this.bounds.center; | ||
const scale = this.bounds.scale; | ||
min.set(+Infinity); | ||
max.set(-Infinity); | ||
for (let i = 0, l = array.length; i < l; i += 3) { | ||
const x = array[i]; | ||
const y = array[i + 1]; | ||
const z = array[i + 2]; | ||
min.x = Math.min(x, min.x); | ||
min.y = Math.min(y, min.y); | ||
min.z = Math.min(z, min.z); | ||
max.x = Math.max(x, max.x); | ||
max.y = Math.max(y, max.y); | ||
max.z = Math.max(z, max.z); | ||
} | ||
scale.sub(max, min); | ||
center.add(min, max).divide(2); | ||
// This is not an accurate radius - use computeBoundingSphere if accuracy needed | ||
this.bounds.radius = tempVec3.copy(scale).divide(2).length(); | ||
} | ||
computeBoundingSphere(array) { | ||
// Use position buffer if available | ||
if (!array && this.geometry.attributes.position) array = this.geometry.attributes.position.data; | ||
if (!array) console.warn('No position buffer found to compute bounds'); | ||
if (!this.bounds) this.computeBoundingBox(array); | ||
let maxRadiusSq = 0; | ||
for (let i = 0, l = array.length; i < l; i += 3) { | ||
tempVec3.fromArray(array, i); | ||
maxRadiusSq = Math.max(maxRadiusSq, this.bounds.center.squaredDistance(tempVec3)); | ||
} | ||
this.bounds.radius = Math.sqrt(maxRadiusSq); | ||
} | ||
} |
@@ -51,3 +51,3 @@ // TODO: upload empty texture if null ? maybe not | ||
if (gl.getShaderInfoLog(vertexShader) !== '') { | ||
console.warn(`${gl.getShaderInfoLog(vertexShader)}\nVertex Shader\n${addLineNumbers(vertexShader)}`); | ||
console.warn(`${gl.getShaderInfoLog(vertexShader)}\nVertex Shader\n${addLineNumbers(vertex)}`); | ||
} | ||
@@ -60,3 +60,3 @@ | ||
if (gl.getShaderInfoLog(fragmentShader) !== '') { | ||
console.warn(`${gl.getShaderInfoLog(fragmentShader)}\nFragment Shader\n${addLineNumbers(fragmentShader)}`); | ||
console.warn(`${gl.getShaderInfoLog(fragmentShader)}\nFragment Shader\n${addLineNumbers(fragment)}`); | ||
} | ||
@@ -63,0 +63,0 @@ |
import {Mat4} from '../math/Mat4.js'; | ||
import {Vec3} from '../math/Vec3.js'; | ||
// TODO: support frustum culling ? | ||
// TODO: Handle context loss https://www.khronos.org/webgl/wiki/HandlingContextLost | ||
// TODO: extend sorting to be more customisable | ||
// sort into 3 groups: | ||
// - opaque (depthTest: true, depthWrite: true) | ||
// - then sort by program | ||
// - then sort by z-depth (front to back) | ||
// - then sort by texture | ||
// - transparent (depthTest: true, depthWrite: false) | ||
// - ui (depthTest: false, depthWrite: false) | ||
@@ -23,3 +15,2 @@ // Not automatic - devs to use these methods manually | ||
const projMat4 = new Mat4(); | ||
const tempVec3 = new Vec3(); | ||
@@ -41,3 +32,2 @@ | ||
autoClear = true, | ||
sort = true, | ||
} = {}) { | ||
@@ -52,3 +42,2 @@ const attributes = {alpha, depth, stencil, antialias, premultipliedAlpha, preserveDrawingBuffer, powerPreference}; | ||
this.autoClear = autoClear; | ||
this.sort = sort; | ||
@@ -219,55 +208,76 @@ // Attempt WebGL2, otherwise fallback to WebGL1 | ||
drawOpaque(scene, camera) { | ||
const opaque = []; | ||
scene.traverse(node => { | ||
if (!node.program || node.program.transparent) return; | ||
opaque.splice(getRenderOrder(node), 0, node); | ||
}); | ||
function getRenderOrder(node) { | ||
node.worldMatrix.getTranslation(tempVec3); | ||
tempVec3.applyMatrix4(projMat4); | ||
node.zDepth = tempVec3.z; | ||
for (let i = 0; i < opaque.length; i++) { | ||
if (node.zDepth <= opaque[i].zDepth) return i; | ||
} | ||
return opaque.length; | ||
sortOpaque(a, b) { | ||
if (a.renderOrder !== b.renderOrder) { | ||
return a.renderOrder - b.renderOrder; | ||
} else if (a.program.id !== b.program.id) { | ||
return a.program.id - b.program.id; | ||
} else if (a.zDepth !== b.zDepth) { | ||
return a.zDepth - b.zDepth; | ||
} else { | ||
return b.id - a.id; | ||
} | ||
} | ||
for (let i = 0; i < opaque.length; i++) { | ||
opaque[i].draw({camera}); | ||
sortTransparent(a, b) { | ||
if (a.renderOrder !== b.renderOrder) { | ||
return a.renderOrder - b.renderOrder; | ||
} if (a.zDepth !== b.zDepth) { | ||
return b.zDepth - a.zDepth; | ||
} else { | ||
return b.id - a.id; | ||
} | ||
} | ||
drawTransparent(scene, camera) { | ||
const transparent = []; | ||
const custom = []; | ||
getRenderList({scene, camera, frustumCull, sort}) { | ||
let renderList = []; | ||
if (camera && frustumCull) camera.updateFrustum(); | ||
// Get visible | ||
scene.traverse(node => { | ||
if (!node.program || !node.program.transparent) return; | ||
if (!node.visible) return true; | ||
if (!node.draw) return; | ||
// If manual order set, add to queue last | ||
if (node.renderOrder !== undefined) return custom.push(node); | ||
transparent.splice(getRenderOrder(node), 0, node); | ||
if (frustumCull && node.frustumCulled && camera) { | ||
if (!camera.frustumIntersectsMesh(node)) return; | ||
} | ||
renderList.push(node); | ||
}); | ||
function getRenderOrder(node) { | ||
node.worldMatrix.getTranslation(tempVec3); | ||
tempVec3.applyMatrix4(projMat4); | ||
if (sort) { | ||
const opaque = []; | ||
const transparent = []; // depthTest true | ||
const ui = []; // depthTest false | ||
node.zDepth = tempVec3.z; | ||
for (let i = 0; i < transparent.length; i++) { | ||
if (node.zDepth >= transparent[i].zDepth) return i; | ||
} | ||
return transparent.length; | ||
} | ||
renderList.forEach(node => { | ||
// Add manually ordered nodes | ||
for (let i = 0; i < custom.length; i++) { | ||
transparent.splice(custom[i].renderOrder, 0, custom[i]); | ||
// Split into the 3 render groups | ||
if (!node.program.transparent) { | ||
opaque.push(node); | ||
} else if (node.program.depthTest) { | ||
transparent.push(node); | ||
} else { | ||
ui.push(node); | ||
} | ||
node.zDepth = 0; | ||
// Only calculate z-depth if renderOrder unset and depthTest is true | ||
if (node.renderOrder !== 0 || !node.program.depthTest || !camera) return; | ||
// update z-depth | ||
node.worldMatrix.getTranslation(tempVec3); | ||
tempVec3.applyMatrix4(camera.projectionViewMatrix); | ||
node.zDepth = tempVec3.z; | ||
}); | ||
opaque.sort(this.sortOpaque); | ||
transparent.sort(this.sortTransparent); | ||
ui.sort(this.sortTransparent); | ||
renderList = opaque.concat(transparent, ui); | ||
} | ||
for (let i = 0; i < transparent.length; i++) { | ||
transparent[i].draw({camera}); | ||
} | ||
return renderList; | ||
} | ||
@@ -280,2 +290,4 @@ | ||
update = true, | ||
sort = true, | ||
frustumCull = true, | ||
}) { | ||
@@ -311,14 +323,9 @@ | ||
// can only sort if camera available | ||
if (this.sort && camera) { | ||
projMat4.multiply(camera.projectionMatrix, camera.viewMatrix); | ||
this.drawOpaque(scene, camera); | ||
this.drawTransparent(scene, camera); | ||
} else { | ||
scene.traverse(node => { | ||
if (!node.draw) return; | ||
node.draw({camera}); | ||
}); | ||
} | ||
// Get render list - entails culling and sorting | ||
const renderList = this.getRenderList({scene, camera, frustumCull, sort}); | ||
renderList.forEach(node => { | ||
node.draw({camera}); | ||
}); | ||
} | ||
} |
@@ -10,2 +10,3 @@ import {Vec3} from '../math/Vec3.js'; | ||
this.children = []; | ||
this.visible = true; | ||
@@ -26,16 +27,16 @@ this.matrix = new Mat4(); | ||
setParent(parent, notifyChild = true) { | ||
if (!parent && notifyChild) this.parent.removeChild(this, false); | ||
setParent(parent, notifyParent = true) { | ||
if (notifyParent && this.parent && parent !== this.parent) this.parent.removeChild(this, false); | ||
this.parent = parent; | ||
if (parent && notifyChild) parent.addChild(this, false); | ||
if (notifyParent && parent) parent.addChild(this, false); | ||
} | ||
addChild(child, notifyParent = true) { | ||
addChild(child, notifyChild = true) { | ||
if (!~this.children.indexOf(child)) this.children.push(child); | ||
if (notifyParent) child.setParent(this, false); | ||
if (notifyChild) child.setParent(this, false); | ||
} | ||
removeChild(child, notifyParent = true) { | ||
removeChild(child, notifyChild = true) { | ||
if (!!~this.children.indexOf(child)) this.children.splice(this.children.indexOf(child), 1); | ||
if (notifyParent) child.setParent(null, false); | ||
if (notifyChild) child.setParent(null, false); | ||
} | ||
@@ -64,3 +65,5 @@ | ||
traverse(callback) { | ||
callback(this); | ||
// Return true in callback to stop traversing children | ||
if (callback(this)) return; | ||
for (let i = 0, l = this.children.length; i < l; i ++) { | ||
@@ -67,0 +70,0 @@ this.children[i].traverse(callback); |
@@ -42,10 +42,7 @@ // TODO: test orthographic | ||
meshes.forEach(mesh => { | ||
if (!mesh.bounds) { | ||
if (mesh.raycast === 'sphere') { | ||
mesh.computeBoundingSphere(); | ||
} else { | ||
mesh.computeBoundingBox(); | ||
} | ||
} | ||
// Create bounds | ||
if (!mesh.geometry.bounds) mesh.geometry.computeBoundingBox(); | ||
if (mesh.geometry.raycast === 'sphere' && mesh.geometry.bounds === Infinity) mesh.geometry.computeBoundingSphere(); | ||
// Take world space ray and make it object space to align with bounding box | ||
@@ -57,6 +54,6 @@ invWorldMat4.inverse(mesh.worldMatrix); | ||
let distance = 0; | ||
if (mesh.raycast === 'sphere') { | ||
distance = this.intersectSphere(mesh.bounds, origin, direction); | ||
if (mesh.geometry.raycast === 'sphere') { | ||
distance = this.intersectSphere(mesh.geometry.bounds, origin, direction); | ||
} else { | ||
distance = this.intersectBox(mesh.bounds, origin, direction); | ||
distance = this.intersectBox(mesh.geometry.bounds, origin, direction); | ||
} | ||
@@ -98,3 +95,3 @@ if (!distance) return; | ||
// AABB - Axis aligned bounding box testing | ||
// Ray AABB - Ray Axis aligned bounding box testing | ||
intersectBox(box, origin = this.origin, direction = this.direction) { | ||
@@ -118,4 +115,4 @@ let tmin, tmax, tYmin, tYmax, tZmin, tZmax; | ||
if ( tYmin > tmin || tmin !== tmin ) tmin = tYmin; | ||
if ( tYmax < tmax || tmax !== tmax ) tmax = tYmax; | ||
if (tYmin > tmin) tmin = tYmin; | ||
if (tYmax < tmax) tmax = tYmax; | ||
@@ -126,4 +123,4 @@ tZmin = ((invdirz >= 0 ? min.z : max.z) - origin.z) * invdirz; | ||
if ((tmin > tZmax) || (tZmin > tmax)) return 0; | ||
if (tZmin > tmin || tmin !== tmin) tmin = tZmin; | ||
if (tZmax < tmax || tmax !== tmax) tmax = tZmax; | ||
if (tZmin > tmin) tmin = tZmin; | ||
if (tZmax < tmax) tmax = tZmax; | ||
@@ -130,0 +127,0 @@ if (tmax < 0) return 0; |
@@ -920,2 +920,20 @@ const EPSILON = 0.000001; | ||
export function getMaxScaleOnAxis(mat) { | ||
let m11 = mat[0]; | ||
let m12 = mat[1]; | ||
let m13 = mat[2]; | ||
let m21 = mat[4]; | ||
let m22 = mat[5]; | ||
let m23 = mat[6]; | ||
let m31 = mat[8]; | ||
let m32 = mat[9]; | ||
let m33 = mat[10]; | ||
const x = m11 * m11 + m12 * m12 + m13 * m13; | ||
const y = m21 * m21 + m22 * m22 + m23 * m23; | ||
const z = m31 * m31 + m32 * m32 + m33 * m33; | ||
return Math.sqrt(Math.max(x, y, z)); | ||
} | ||
/** | ||
@@ -922,0 +940,0 @@ * Returns a quaternion representing the rotational component |
@@ -140,2 +140,6 @@ import * as Mat4Func from './functions/Mat4Func.js'; | ||
getMaxScaleOnAxis() { | ||
return Mat4Func.getMaxScaleOnAxis(this); | ||
} | ||
lookAt(eye, target, up) { | ||
@@ -142,0 +146,0 @@ Mat4Func.targetTo(this, eye, target, up); |
@@ -47,2 +47,7 @@ import * as Vec4Func from './functions/Vec4Func.js'; | ||
normalize() { | ||
Vec4Func.normalize(this, this); | ||
return this; | ||
} | ||
fromArray(a, o = 0) { | ||
@@ -49,0 +54,0 @@ this[0] = a[o]; |
Sorry, the diff of this file is not supported yet
15027473
153
20643