224 lines
6.5 KiB
JavaScript
224 lines
6.5 KiB
JavaScript
import Screen from './screen'
|
|
import { Node } from './components'
|
|
import { glMatrix, mat4, vec2, vec3, vec4 } from 'gl-matrix'
|
|
|
|
const SPEED = 100.0
|
|
const SENSITIVTY = 100.0
|
|
const FOV = 45.0
|
|
const ZNEAR = 0.1
|
|
const ZFAR = 10000.0
|
|
|
|
class Frustum {
|
|
constructor () {
|
|
this.planes = []
|
|
}
|
|
|
|
construct (projectionMatrix) {
|
|
const me = projectionMatrix
|
|
|
|
this.planes = [
|
|
vec4.normalize([], [me[3] - me[0], me[7] - me[4], me[11] - me[8], me[15] - me[12]]),
|
|
vec4.normalize([], [me[3] + me[0], me[7] + me[4], me[11] + me[8], me[15] + me[12]]),
|
|
vec4.normalize([], [me[3] + me[1], me[7] + me[5], me[11] + me[9], me[15] + me[13]]),
|
|
vec4.normalize([], [me[3] - me[1], me[7] - me[5], me[11] - me[9], me[15] - me[13]]),
|
|
vec4.normalize([], [me[3] - me[2], me[7] - me[6], me[11] - me[10], me[15] - me[14]]),
|
|
vec4.normalize([], [me[3] + me[2], me[7] + me[6], me[11] + me[10], me[15] + me[14]])
|
|
]
|
|
}
|
|
|
|
// Calculates the closest distance from a given point to a given clipping plane
|
|
distanceToPlane (plane, point) {
|
|
return vec3.dot(this.planes[plane], point) + this.planes[plane][3]
|
|
}
|
|
|
|
containsPoint (point) {
|
|
// For each plane; return outside if the point is behind the plane
|
|
for (let i = 0; i < 6; i++) {
|
|
if (this.distanceToPlane(i, point) <= 0.0) return false
|
|
}
|
|
|
|
// Return inside
|
|
return true
|
|
}
|
|
|
|
containsSphere (position, radius) {
|
|
// Plane counter
|
|
let planeCount = 0
|
|
|
|
// Use the point-to-plane distance to calculate the number of planes the sphere is in front of
|
|
for (let i = 0; i < 6; i++) {
|
|
const distance = this.distanceToPlane(i, position)
|
|
if (distance <= -radius) {
|
|
return 0
|
|
} else if (distance > radius) {
|
|
planeCount++
|
|
}
|
|
}
|
|
|
|
// Return inside if in front of all planes; otherwise intersecting
|
|
return planeCount === 6 ? 1 : 2
|
|
}
|
|
|
|
containsBox (min, max) {
|
|
// Build a vector holding all box corners
|
|
const points = []
|
|
for (let i = 0; i < 8; i++) {
|
|
points.push([i < 4 ? max[0] : min[0], i % 4 < 2 ? max[1] : min[1], i % 2 ? max[2] : min[2]])
|
|
}
|
|
|
|
// Test the box as a polygon
|
|
return this.containsPolygon(points)
|
|
}
|
|
|
|
containsPolygon (points) {
|
|
// Plane counter
|
|
let planeCount = 0
|
|
|
|
// Use the point-to-plane distance to calculate the number of planes the polygon is in front of
|
|
for (let i = 0; i < 6; i++) {
|
|
let pointCount = 0
|
|
for (let j = 0; j < points.length; j++) {
|
|
if (this.distanceToPlane(i, points[j]) > 0.0) {
|
|
pointCount++
|
|
}
|
|
}
|
|
if (pointCount === 0) {
|
|
return 0
|
|
} else if (pointCount === points.length) {
|
|
planeCount++
|
|
}
|
|
}
|
|
|
|
// Return inside if in front of all planes; otherwise intersecting
|
|
return planeCount === 6 ? 1 : 2
|
|
}
|
|
}
|
|
|
|
class Camera extends Node {
|
|
constructor (pos, rotation) {
|
|
super(pos, null, rotation)
|
|
this.fov = FOV
|
|
this.speed = SPEED
|
|
this.sensitivity = SENSITIVTY
|
|
|
|
// Create an empty projection matrix
|
|
this.projection = mat4.create()
|
|
|
|
// Helping vectors for calculating the view matrix
|
|
this.up = vec3.create()
|
|
this.front = vec3.fromValues(0.0, 0.0, -1.0)
|
|
this.right = vec3.create()
|
|
this.worldUp = vec3.fromValues(0.0, 1.0, 0.0)
|
|
|
|
this.nearPlane = ZNEAR
|
|
this.farPlane = ZFAR
|
|
|
|
// Frustum planes
|
|
this.frustum = new Frustum()
|
|
|
|
this.updateTransform()
|
|
}
|
|
|
|
processKeyboard (direction, delta) {
|
|
const newSpeed = this.speed * delta
|
|
const velocity = vec3.fromValues(newSpeed, newSpeed, newSpeed)
|
|
const vec = vec3.create()
|
|
|
|
if (direction === 0) {
|
|
vec3.multiply(vec, this.front, velocity)
|
|
vec3.add(this.pos, this.pos, vec)
|
|
}
|
|
|
|
if (direction === 1) {
|
|
vec3.multiply(vec, this.front, velocity)
|
|
vec3.sub(this.pos, this.pos, vec)
|
|
}
|
|
|
|
if (direction === 2) {
|
|
vec3.multiply(vec, this.right, velocity)
|
|
vec3.sub(this.pos, this.pos, vec)
|
|
}
|
|
|
|
if (direction === 3) {
|
|
vec3.multiply(vec, this.right, velocity)
|
|
vec3.add(this.pos, this.pos, vec)
|
|
}
|
|
|
|
this.frustum.construct(mat4.multiply([], this.projection, this.view))
|
|
}
|
|
|
|
processMouseMove (offset, constrain = true) {
|
|
const fst = vec2.fromValues(offset.x * -this.sensitivity, offset.y * this.sensitivity)
|
|
this.rotation[0] += glMatrix.toRadian(fst[0])
|
|
this.rotation[1] += glMatrix.toRadian(fst[1])
|
|
|
|
// Make sure that when pitch is out of bounds, screen doesn't get flipped
|
|
if (constrain) {
|
|
if (this.rotation[1] > glMatrix.toRadian(89.0)) {
|
|
this.rotation[1] = glMatrix.toRadian(89.0)
|
|
}
|
|
|
|
if (this.rotation[1] < -glMatrix.toRadian(89.0)) {
|
|
this.rotation[1] = -glMatrix.toRadian(89.0)
|
|
}
|
|
}
|
|
|
|
this.updateTransform()
|
|
}
|
|
|
|
// Calculate the vertices required for the view matrix
|
|
updateTransform () {
|
|
// Prevent premature call (from super class)
|
|
if (!this.front || !this.worldUp) return
|
|
|
|
// Calculate the new Front vector
|
|
const front = vec3.create()
|
|
|
|
front[0] = Math.cos(this.rotation[0]) * Math.cos(this.rotation[1])
|
|
front[1] = Math.sin(this.rotation[1])
|
|
front[2] = Math.sin(this.rotation[0]) * Math.cos(this.rotation[1])
|
|
|
|
vec3.normalize(this.front, front)
|
|
|
|
// Also re-calculate the Right and Up vector
|
|
// Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
|
|
const rightCross = vec3.create()
|
|
const upCross = vec3.create()
|
|
|
|
vec3.cross(rightCross, this.front, this.worldUp)
|
|
vec3.normalize(this.right, rightCross)
|
|
|
|
vec3.cross(upCross, this.right, this.front)
|
|
vec3.normalize(this.up, upCross)
|
|
|
|
this.frustum.construct(mat4.multiply([], this.projection, this.view))
|
|
}
|
|
|
|
updateProjection (gl) {
|
|
mat4.perspective(this.projection, this.fov, Screen.aspectRatio, this.nearPlane, this.farPlane)
|
|
this.updateTransform()
|
|
}
|
|
|
|
// Calculate the view matrix on-the-go
|
|
// Really no advantage in storing this
|
|
get view () {
|
|
const mat = mat4.create()
|
|
const center = vec3.create()
|
|
vec3.add(center, this.pos, this.front)
|
|
mat4.lookAt(mat, this.pos, center, this.up)
|
|
return mat
|
|
}
|
|
|
|
// Override the default draw method because we don't need to draw the camera,
|
|
// instead set the projection and view matrices
|
|
draw (gl, shader) {
|
|
const projloc = shader.getUniformLocation(gl, 'uProjectionMatrix')
|
|
const viewloc = shader.getUniformLocation(gl, 'uViewMatrix')
|
|
|
|
gl.uniformMatrix4fv(projloc, false, this.projection)
|
|
gl.uniformMatrix4fv(viewloc, false, this.view)
|
|
}
|
|
}
|
|
|
|
export default Camera
|