Newer
Older
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4, Vec3, Vec4, EPSILON } from 'mol-math/linear-algebra'
export type Viewport = {
x: number
y: number
width: number
height: number
}
export namespace Viewport {
export function create(x: number, y: number, width: number, height: number): Viewport {
return { x, y, width, height }
}
export function clone(viewport: Viewport): Viewport {
return { ...viewport }
}
export function copy(target: Viewport, source: Viewport): Viewport {
return Object.assign(target, source)
}
export function set(viewport: Viewport, x: number, y: number, width: number, height: number): Viewport {
viewport.x = x
viewport.y = y
viewport.width = width
viewport.height = height
return viewport
}
export function toVec4(v4: Vec4, viewport: Viewport): Vec4 {
v4[0] = viewport.x
v4[1] = viewport.y
v4[2] = viewport.width
v4[3] = viewport.height
return v4
}
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
}
const tmpVec3 = Vec3.zero()
/** Modifies the direction & up vectors in place */
export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target: Vec3) {
Vec3.sub(tmpVec3, target, position)
Vec3.normalize(tmpVec3, tmpVec3)
if (!Vec3.isZero(tmpVec3)) {
// change direction vector to look at target
const d = Vec3.dot(tmpVec3, up)
if (Math.abs(d - 1) < EPSILON.Value) { // parallel
Vec3.scale(up, direction, -1)
} else if (Math.abs(d + 1) < EPSILON.Value) { // anti parallel
Vec3.copy(up, direction)
}
Vec3.copy(direction, tmpVec3)
// normalize up vector
Vec3.cross(tmpVec3, direction, up)
Vec3.normalize(tmpVec3, tmpVec3)
Vec3.cross(up, tmpVec3, direction)
Vec3.normalize(up, up)
}
}
const NEAR_RANGE = 0
const FAR_RANGE = 1
const tmpVec4 = Vec4.zero()
/** Transform point into 2D window coordinates. */
export function cameraProject (out: Vec4, point: Vec3, viewport: Viewport, projectionView: Mat4) {
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport
// clip space -> NDC -> window coordinates, implicit 1.0 for w component
Vec4.set(tmpVec4, point[0], point[1], point[2], 1.0)
// transform into clip space
Vec4.transformMat4(tmpVec4, tmpVec4, projectionView)
// transform into NDC
const w = tmpVec4[3]
if (w !== 0) {
tmpVec4[0] /= w
tmpVec4[1] /= w
tmpVec4[2] /= w
}
// transform into window coordinates, set fourth component is (1/clip.w) as in gl_FragCoord.w
out[0] = vX + vWidth / 2 * tmpVec4[0] + (0 + vWidth / 2)
out[1] = vY + vHeight / 2 * tmpVec4[1] + (0 + vHeight / 2)
out[2] = (FAR_RANGE - NEAR_RANGE) / 2 * tmpVec4[2] + (FAR_RANGE + NEAR_RANGE) / 2
out[3] = w === 0 ? 0 : 1 / w
return out
}
/**
* Transform point from screen space to 3D coordinates.
* The point must have x and y set to 2D window coordinates and z between 0 (near) and 1 (far).
*/
export function cameraUnproject (out: Vec3, point: Vec3, viewport: Viewport, inverseProjectionView: Mat4) {
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport
const x = point[0] - vX
const y = (vHeight - point[1] - 1) - vY
const z = point[2]
out[0] = (2 * x) / vWidth - 1
out[1] = (2 * y) / vHeight - 1
out[2] = 2 * z - 1
return Vec3.transformMat4(out, out, inverseProjectionView)
}