StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
StackGenVis/frontend/node_modules/turntable-camera-controller/turntable.js

572 lines
12 KiB

'use strict'
module.exports = createTurntableController
var filterVector = require('filtered-vector')
var invert44 = require('gl-mat4/invert')
var rotateM = require('gl-mat4/rotate')
var cross = require('gl-vec3/cross')
var normalize3 = require('gl-vec3/normalize')
var dot3 = require('gl-vec3/dot')
function len3(x, y, z) {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2))
}
function clamp1(x) {
return Math.min(1.0, Math.max(-1.0, x))
}
function findOrthoPair(v) {
var vx = Math.abs(v[0])
var vy = Math.abs(v[1])
var vz = Math.abs(v[2])
var u = [0,0,0]
if(vx > Math.max(vy, vz)) {
u[2] = 1
} else if(vy > Math.max(vx, vz)) {
u[0] = 1
} else {
u[1] = 1
}
var vv = 0
var uv = 0
for(var i=0; i<3; ++i ) {
vv += v[i] * v[i]
uv += u[i] * v[i]
}
for(var i=0; i<3; ++i) {
u[i] -= (uv / vv) * v[i]
}
normalize3(u, u)
return u
}
function TurntableController(zoomMin, zoomMax, center, up, right, radius, theta, phi) {
this.center = filterVector(center)
this.up = filterVector(up)
this.right = filterVector(right)
this.radius = filterVector([radius])
this.angle = filterVector([theta, phi])
this.angle.bounds = [[-Infinity,-Math.PI/2], [Infinity,Math.PI/2]]
this.setDistanceLimits(zoomMin, zoomMax)
this.computedCenter = this.center.curve(0)
this.computedUp = this.up.curve(0)
this.computedRight = this.right.curve(0)
this.computedRadius = this.radius.curve(0)
this.computedAngle = this.angle.curve(0)
this.computedToward = [0,0,0]
this.computedEye = [0,0,0]
this.computedMatrix = new Array(16)
for(var i=0; i<16; ++i) {
this.computedMatrix[i] = 0.5
}
this.recalcMatrix(0)
}
var proto = TurntableController.prototype
proto.setDistanceLimits = function(minDist, maxDist) {
if(minDist > 0) {
minDist = Math.log(minDist)
} else {
minDist = -Infinity
}
if(maxDist > 0) {
maxDist = Math.log(maxDist)
} else {
maxDist = Infinity
}
maxDist = Math.max(maxDist, minDist)
this.radius.bounds[0][0] = minDist
this.radius.bounds[1][0] = maxDist
}
proto.getDistanceLimits = function(out) {
var bounds = this.radius.bounds[0]
if(out) {
out[0] = Math.exp(bounds[0][0])
out[1] = Math.exp(bounds[1][0])
return out
}
return [ Math.exp(bounds[0][0]), Math.exp(bounds[1][0]) ]
}
proto.recalcMatrix = function(t) {
//Recompute curves
this.center.curve(t)
this.up.curve(t)
this.right.curve(t)
this.radius.curve(t)
this.angle.curve(t)
//Compute frame for camera matrix
var up = this.computedUp
var right = this.computedRight
var uu = 0.0
var ur = 0.0
for(var i=0; i<3; ++i) {
ur += up[i] * right[i]
uu += up[i] * up[i]
}
var ul = Math.sqrt(uu)
var rr = 0.0
for(var i=0; i<3; ++i) {
right[i] -= up[i] * ur / uu
rr += right[i] * right[i]
up[i] /= ul
}
var rl = Math.sqrt(rr)
for(var i=0; i<3; ++i) {
right[i] /= rl
}
//Compute toward vector
var toward = this.computedToward
cross(toward, up, right)
normalize3(toward, toward)
//Compute angular parameters
var radius = Math.exp(this.computedRadius[0])
var theta = this.computedAngle[0]
var phi = this.computedAngle[1]
var ctheta = Math.cos(theta)
var stheta = Math.sin(theta)
var cphi = Math.cos(phi)
var sphi = Math.sin(phi)
var center = this.computedCenter
var wx = ctheta * cphi
var wy = stheta * cphi
var wz = sphi
var sx = -ctheta * sphi
var sy = -stheta * sphi
var sz = cphi
var eye = this.computedEye
var mat = this.computedMatrix
for(var i=0; i<3; ++i) {
var x = wx * right[i] + wy * toward[i] + wz * up[i]
mat[4*i+1] = sx * right[i] + sy * toward[i] + sz * up[i]
mat[4*i+2] = x
mat[4*i+3] = 0.0
}
var ax = mat[1]
var ay = mat[5]
var az = mat[9]
var bx = mat[2]
var by = mat[6]
var bz = mat[10]
var cx = ay * bz - az * by
var cy = az * bx - ax * bz
var cz = ax * by - ay * bx
var cl = len3(cx, cy, cz)
cx /= cl
cy /= cl
cz /= cl
mat[0] = cx
mat[4] = cy
mat[8] = cz
for(var i=0; i<3; ++i) {
eye[i] = center[i] + mat[2+4*i]*radius
}
for(var i=0; i<3; ++i) {
var rr = 0.0
for(var j=0; j<3; ++j) {
rr += mat[i+4*j] * eye[j]
}
mat[12+i] = -rr
}
mat[15] = 1.0
}
proto.getMatrix = function(t, result) {
this.recalcMatrix(t)
var mat = this.computedMatrix
if(result) {
for(var i=0; i<16; ++i) {
result[i] = mat[i]
}
return result
}
return mat
}
var zAxis = [0,0,0]
proto.rotate = function(t, dtheta, dphi, droll) {
this.angle.move(t, dtheta, dphi)
if(droll) {
this.recalcMatrix(t)
var mat = this.computedMatrix
zAxis[0] = mat[2]
zAxis[1] = mat[6]
zAxis[2] = mat[10]
var up = this.computedUp
var right = this.computedRight
var toward = this.computedToward
for(var i=0; i<3; ++i) {
mat[4*i] = up[i]
mat[4*i+1] = right[i]
mat[4*i+2] = toward[i]
}
rotateM(mat, mat, droll, zAxis)
for(var i=0; i<3; ++i) {
up[i] = mat[4*i]
right[i] = mat[4*i+1]
}
this.up.set(t, up[0], up[1], up[2])
this.right.set(t, right[0], right[1], right[2])
}
}
proto.pan = function(t, dx, dy, dz) {
dx = dx || 0.0
dy = dy || 0.0
dz = dz || 0.0
this.recalcMatrix(t)
var mat = this.computedMatrix
var dist = Math.exp(this.computedRadius[0])
var ux = mat[1]
var uy = mat[5]
var uz = mat[9]
var ul = len3(ux, uy, uz)
ux /= ul
uy /= ul
uz /= ul
var rx = mat[0]
var ry = mat[4]
var rz = mat[8]
var ru = rx * ux + ry * uy + rz * uz
rx -= ux * ru
ry -= uy * ru
rz -= uz * ru
var rl = len3(rx, ry, rz)
rx /= rl
ry /= rl
rz /= rl
var vx = rx * dx + ux * dy
var vy = ry * dx + uy * dy
var vz = rz * dx + uz * dy
this.center.move(t, vx, vy, vz)
//Update z-component of radius
var radius = Math.exp(this.computedRadius[0])
radius = Math.max(1e-4, radius + dz)
this.radius.set(t, Math.log(radius))
}
proto.translate = function(t, dx, dy, dz) {
this.center.move(t,
dx||0.0,
dy||0.0,
dz||0.0)
}
//Recenters the coordinate axes
proto.setMatrix = function(t, mat, axes, noSnap) {
//Get the axes for tare
var ushift = 1
if(typeof axes === 'number') {
ushift = (axes)|0
}
if(ushift < 0 || ushift > 3) {
ushift = 1
}
var vshift = (ushift + 2) % 3
var fshift = (ushift + 1) % 3
//Recompute state for new t value
if(!mat) {
this.recalcMatrix(t)
mat = this.computedMatrix
}
//Get right and up vectors
var ux = mat[ushift]
var uy = mat[ushift+4]
var uz = mat[ushift+8]
if(!noSnap) {
var ul = len3(ux, uy, uz)
ux /= ul
uy /= ul
uz /= ul
} else {
var ax = Math.abs(ux)
var ay = Math.abs(uy)
var az = Math.abs(uz)
var am = Math.max(ax,ay,az)
if(ax === am) {
ux = (ux < 0) ? -1 : 1
uy = uz = 0
} else if(az === am) {
uz = (uz < 0) ? -1 : 1
ux = uy = 0
} else {
uy = (uy < 0) ? -1 : 1
ux = uz = 0
}
}
var rx = mat[vshift]
var ry = mat[vshift+4]
var rz = mat[vshift+8]
var ru = rx * ux + ry * uy + rz * uz
rx -= ux * ru
ry -= uy * ru
rz -= uz * ru
var rl = len3(rx, ry, rz)
rx /= rl
ry /= rl
rz /= rl
var fx = uy * rz - uz * ry
var fy = uz * rx - ux * rz
var fz = ux * ry - uy * rx
var fl = len3(fx, fy, fz)
fx /= fl
fy /= fl
fz /= fl
this.center.jump(t, ex, ey, ez)
this.radius.idle(t)
this.up.jump(t, ux, uy, uz)
this.right.jump(t, rx, ry, rz)
var phi, theta
if(ushift === 2) {
var cx = mat[1]
var cy = mat[5]
var cz = mat[9]
var cr = cx * rx + cy * ry + cz * rz
var cf = cx * fx + cy * fy + cz * fz
if(tu < 0) {
phi = -Math.PI/2
} else {
phi = Math.PI/2
}
theta = Math.atan2(cf, cr)
} else {
var tx = mat[2]
var ty = mat[6]
var tz = mat[10]
var tu = tx * ux + ty * uy + tz * uz
var tr = tx * rx + ty * ry + tz * rz
var tf = tx * fx + ty * fy + tz * fz
phi = Math.asin(clamp1(tu))
theta = Math.atan2(tf, tr)
}
this.angle.jump(t, theta, phi)
this.recalcMatrix(t)
var dx = mat[2]
var dy = mat[6]
var dz = mat[10]
var imat = this.computedMatrix
invert44(imat, mat)
var w = imat[15]
var ex = imat[12] / w
var ey = imat[13] / w
var ez = imat[14] / w
var gs = Math.exp(this.computedRadius[0])
this.center.jump(t, ex-dx*gs, ey-dy*gs, ez-dz*gs)
}
proto.lastT = function() {
return Math.max(
this.center.lastT(),
this.up.lastT(),
this.right.lastT(),
this.radius.lastT(),
this.angle.lastT())
}
proto.idle = function(t) {
this.center.idle(t)
this.up.idle(t)
this.right.idle(t)
this.radius.idle(t)
this.angle.idle(t)
}
proto.flush = function(t) {
this.center.flush(t)
this.up.flush(t)
this.right.flush(t)
this.radius.flush(t)
this.angle.flush(t)
}
proto.setDistance = function(t, d) {
if(d > 0) {
this.radius.set(t, Math.log(d))
}
}
proto.lookAt = function(t, eye, center, up) {
this.recalcMatrix(t)
eye = eye || this.computedEye
center = center || this.computedCenter
up = up || this.computedUp
var ux = up[0]
var uy = up[1]
var uz = up[2]
var ul = len3(ux, uy, uz)
if(ul < 1e-6) {
return
}
ux /= ul
uy /= ul
uz /= ul
var tx = eye[0] - center[0]
var ty = eye[1] - center[1]
var tz = eye[2] - center[2]
var tl = len3(tx, ty, tz)
if(tl < 1e-6) {
return
}
tx /= tl
ty /= tl
tz /= tl
var right = this.computedRight
var rx = right[0]
var ry = right[1]
var rz = right[2]
var ru = ux*rx + uy*ry + uz*rz
rx -= ru * ux
ry -= ru * uy
rz -= ru * uz
var rl = len3(rx, ry, rz)
if(rl < 0.01) {
rx = uy * tz - uz * ty
ry = uz * tx - ux * tz
rz = ux * ty - uy * tx
rl = len3(rx, ry, rz)
if(rl < 1e-6) {
return
}
}
rx /= rl
ry /= rl
rz /= rl
this.up.set(t, ux, uy, uz)
this.right.set(t, rx, ry, rz)
this.center.set(t, center[0], center[1], center[2])
this.radius.set(t, Math.log(tl))
var fx = uy * rz - uz * ry
var fy = uz * rx - ux * rz
var fz = ux * ry - uy * rx
var fl = len3(fx, fy, fz)
fx /= fl
fy /= fl
fz /= fl
var tu = ux*tx + uy*ty + uz*tz
var tr = rx*tx + ry*ty + rz*tz
var tf = fx*tx + fy*ty + fz*tz
var phi = Math.asin(clamp1(tu))
var theta = Math.atan2(tf, tr)
var angleState = this.angle._state
var lastTheta = angleState[angleState.length-1]
var lastPhi = angleState[angleState.length-2]
lastTheta = lastTheta % (2.0 * Math.PI)
var dp = Math.abs(lastTheta + 2.0 * Math.PI - theta)
var d0 = Math.abs(lastTheta - theta)
var dn = Math.abs(lastTheta - 2.0 * Math.PI - theta)
if(dp < d0) {
lastTheta += 2.0 * Math.PI
}
if(dn < d0) {
lastTheta -= 2.0 * Math.PI
}
this.angle.jump(this.angle.lastT(), lastTheta, lastPhi)
this.angle.set(t, theta, phi)
}
function createTurntableController(options) {
options = options || {}
var center = options.center || [0,0,0]
var up = options.up || [0,1,0]
var right = options.right || findOrthoPair(up)
var radius = options.radius || 1.0
var theta = options.theta || 0.0
var phi = options.phi || 0.0
center = [].slice.call(center, 0, 3)
up = [].slice.call(up, 0, 3)
normalize3(up, up)
right = [].slice.call(right, 0, 3)
normalize3(right, right)
if('eye' in options) {
var eye = options.eye
var toward = [
eye[0]-center[0],
eye[1]-center[1],
eye[2]-center[2]
]
cross(right, toward, up)
if(len3(right[0], right[1], right[2]) < 1e-6) {
right = findOrthoPair(up)
} else {
normalize3(right, right)
}
radius = len3(toward[0], toward[1], toward[2])
var ut = dot3(up, toward) / radius
var rt = dot3(right, toward) / radius
phi = Math.acos(ut)
theta = Math.acos(rt)
}
//Use logarithmic coordinates for radius
radius = Math.log(radius)
//Return the controller
return new TurntableController(
options.zoomMin,
options.zoomMax,
center,
up,
right,
radius,
theta,
phi)
}