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.
393 lines
8.3 KiB
393 lines
8.3 KiB
'use strict'
|
|
|
|
module.exports = createOrbitController
|
|
|
|
var filterVector = require('filtered-vector')
|
|
var lookAt = require('gl-mat4/lookAt')
|
|
var mat4FromQuat = require('gl-mat4/fromQuat')
|
|
var invert44 = require('gl-mat4/invert')
|
|
var quatFromFrame = require('./lib/quatFromFrame')
|
|
|
|
function len3(x,y,z) {
|
|
return Math.sqrt(Math.pow(x,2) + Math.pow(y,2) + Math.pow(z,2))
|
|
}
|
|
|
|
function len4(w,x,y,z) {
|
|
return Math.sqrt(Math.pow(w,2) + Math.pow(x,2) + Math.pow(y,2) + Math.pow(z,2))
|
|
}
|
|
|
|
function normalize4(out, a) {
|
|
var ax = a[0]
|
|
var ay = a[1]
|
|
var az = a[2]
|
|
var aw = a[3]
|
|
var al = len4(ax, ay, az, aw)
|
|
if(al > 1e-6) {
|
|
out[0] = ax/al
|
|
out[1] = ay/al
|
|
out[2] = az/al
|
|
out[3] = aw/al
|
|
} else {
|
|
out[0] = out[1] = out[2] = 0.0
|
|
out[3] = 1.0
|
|
}
|
|
}
|
|
|
|
function OrbitCameraController(initQuat, initCenter, initRadius) {
|
|
this.radius = filterVector([initRadius])
|
|
this.center = filterVector(initCenter)
|
|
this.rotation = filterVector(initQuat)
|
|
|
|
this.computedRadius = this.radius.curve(0)
|
|
this.computedCenter = this.center.curve(0)
|
|
this.computedRotation = this.rotation.curve(0)
|
|
this.computedUp = [0.1,0,0]
|
|
this.computedEye = [0.1,0,0]
|
|
this.computedMatrix = [0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
|
|
|
this.recalcMatrix(0)
|
|
}
|
|
|
|
var proto = OrbitCameraController.prototype
|
|
|
|
proto.lastT = function() {
|
|
return Math.max(
|
|
this.radius.lastT(),
|
|
this.center.lastT(),
|
|
this.rotation.lastT())
|
|
}
|
|
|
|
proto.recalcMatrix = function(t) {
|
|
this.radius.curve(t)
|
|
this.center.curve(t)
|
|
this.rotation.curve(t)
|
|
|
|
var quat = this.computedRotation
|
|
normalize4(quat, quat)
|
|
|
|
var mat = this.computedMatrix
|
|
mat4FromQuat(mat, quat)
|
|
|
|
var center = this.computedCenter
|
|
var eye = this.computedEye
|
|
var up = this.computedUp
|
|
var radius = Math.exp(this.computedRadius[0])
|
|
|
|
eye[0] = center[0] + radius * mat[2]
|
|
eye[1] = center[1] + radius * mat[6]
|
|
eye[2] = center[2] + radius * mat[10]
|
|
up[0] = mat[1]
|
|
up[1] = mat[5]
|
|
up[2] = mat[9]
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
proto.getMatrix = function(t, result) {
|
|
this.recalcMatrix(t)
|
|
var m = this.computedMatrix
|
|
if(result) {
|
|
for(var i=0; i<16; ++i) {
|
|
result[i] = m[i]
|
|
}
|
|
return result
|
|
}
|
|
return m
|
|
}
|
|
|
|
proto.idle = function(t) {
|
|
this.center.idle(t)
|
|
this.radius.idle(t)
|
|
this.rotation.idle(t)
|
|
}
|
|
|
|
proto.flush = function(t) {
|
|
this.center.flush(t)
|
|
this.radius.flush(t)
|
|
this.rotation.flush(t)
|
|
}
|
|
|
|
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 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 fx = mat[2]
|
|
var fy = mat[6]
|
|
var fz = mat[10]
|
|
var fu = fx * ux + fy * uy + fz * uz
|
|
var fr = fx * rx + fy * ry + fz * rz
|
|
fx -= fu * ux + fr * rx
|
|
fy -= fu * uy + fr * ry
|
|
fz -= fu * uz + fr * rz
|
|
var fl = len3(fx, fy, fz)
|
|
fx /= fl
|
|
fy /= fl
|
|
fz /= fl
|
|
|
|
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.rotate = function(t, dx, dy, dz) {
|
|
this.recalcMatrix(t)
|
|
|
|
dx = dx||0.0
|
|
dy = dy||0.0
|
|
|
|
var mat = this.computedMatrix
|
|
|
|
var rx = mat[0]
|
|
var ry = mat[4]
|
|
var rz = mat[8]
|
|
|
|
var ux = mat[1]
|
|
var uy = mat[5]
|
|
var uz = mat[9]
|
|
|
|
var fx = mat[2]
|
|
var fy = mat[6]
|
|
var fz = mat[10]
|
|
|
|
var qx = dx * rx + dy * ux
|
|
var qy = dx * ry + dy * uy
|
|
var qz = dx * rz + dy * uz
|
|
|
|
var bx = -(fy * qz - fz * qy)
|
|
var by = -(fz * qx - fx * qz)
|
|
var bz = -(fx * qy - fy * qx)
|
|
var bw = Math.sqrt(Math.max(0.0, 1.0 - Math.pow(bx,2) - Math.pow(by,2) - Math.pow(bz,2)))
|
|
var bl = len4(bx, by, bz, bw)
|
|
if(bl > 1e-6) {
|
|
bx /= bl
|
|
by /= bl
|
|
bz /= bl
|
|
bw /= bl
|
|
} else {
|
|
bx = by = bz = 0.0
|
|
bw = 1.0
|
|
}
|
|
|
|
var rotation = this.computedRotation
|
|
var ax = rotation[0]
|
|
var ay = rotation[1]
|
|
var az = rotation[2]
|
|
var aw = rotation[3]
|
|
|
|
var cx = ax*bw + aw*bx + ay*bz - az*by
|
|
var cy = ay*bw + aw*by + az*bx - ax*bz
|
|
var cz = az*bw + aw*bz + ax*by - ay*bx
|
|
var cw = aw*bw - ax*bx - ay*by - az*bz
|
|
|
|
//Apply roll
|
|
if(dz) {
|
|
bx = fx
|
|
by = fy
|
|
bz = fz
|
|
var s = Math.sin(dz) / len3(bx, by, bz)
|
|
bx *= s
|
|
by *= s
|
|
bz *= s
|
|
bw = Math.cos(dx)
|
|
cx = cx*bw + cw*bx + cy*bz - cz*by
|
|
cy = cy*bw + cw*by + cz*bx - cx*bz
|
|
cz = cz*bw + cw*bz + cx*by - cy*bx
|
|
cw = cw*bw - cx*bx - cy*by - cz*bz
|
|
}
|
|
|
|
var cl = len4(cx, cy, cz, cw)
|
|
if(cl > 1e-6) {
|
|
cx /= cl
|
|
cy /= cl
|
|
cz /= cl
|
|
cw /= cl
|
|
} else {
|
|
cx = cy = cz = 0.0
|
|
cw = 1.0
|
|
}
|
|
|
|
this.rotation.set(t, cx, cy, cz, cw)
|
|
}
|
|
|
|
proto.lookAt = function(t, eye, center, up) {
|
|
this.recalcMatrix(t)
|
|
|
|
center = center || this.computedCenter
|
|
eye = eye || this.computedEye
|
|
up = up || this.computedUp
|
|
|
|
var mat = this.computedMatrix
|
|
lookAt(mat, eye, center, up)
|
|
|
|
var rotation = this.computedRotation
|
|
quatFromFrame(rotation,
|
|
mat[0], mat[1], mat[2],
|
|
mat[4], mat[5], mat[6],
|
|
mat[8], mat[9], mat[10])
|
|
normalize4(rotation, rotation)
|
|
this.rotation.set(t, rotation[0], rotation[1], rotation[2], rotation[3])
|
|
|
|
var fl = 0.0
|
|
for(var i=0; i<3; ++i) {
|
|
fl += Math.pow(center[i] - eye[i], 2)
|
|
}
|
|
this.radius.set(t, 0.5 * Math.log(Math.max(fl, 1e-6)))
|
|
|
|
this.center.set(t, center[0], center[1], center[2])
|
|
}
|
|
|
|
proto.translate = function(t, dx, dy, dz) {
|
|
this.center.move(t,
|
|
dx||0.0,
|
|
dy||0.0,
|
|
dz||0.0)
|
|
}
|
|
|
|
proto.setMatrix = function(t, matrix) {
|
|
|
|
var rotation = this.computedRotation
|
|
quatFromFrame(rotation,
|
|
matrix[0], matrix[1], matrix[2],
|
|
matrix[4], matrix[5], matrix[6],
|
|
matrix[8], matrix[9], matrix[10])
|
|
normalize4(rotation, rotation)
|
|
this.rotation.set(t, rotation[0], rotation[1], rotation[2], rotation[3])
|
|
|
|
var mat = this.computedMatrix
|
|
invert44(mat, matrix)
|
|
var w = mat[15]
|
|
if(Math.abs(w) > 1e-6) {
|
|
var cx = mat[12]/w
|
|
var cy = mat[13]/w
|
|
var cz = mat[14]/w
|
|
|
|
this.recalcMatrix(t)
|
|
var r = Math.exp(this.computedRadius[0])
|
|
this.center.set(t, cx-mat[2]*r, cy-mat[6]*r, cz-mat[10]*r)
|
|
this.radius.idle(t)
|
|
} else {
|
|
this.center.idle(t)
|
|
this.radius.idle(t)
|
|
}
|
|
}
|
|
|
|
proto.setDistance = function(t, d) {
|
|
if(d > 0) {
|
|
this.radius.set(t, Math.log(d))
|
|
}
|
|
}
|
|
|
|
proto.setDistanceLimits = function(lo, hi) {
|
|
if(lo > 0) {
|
|
lo = Math.log(lo)
|
|
} else {
|
|
lo = -Infinity
|
|
}
|
|
if(hi > 0) {
|
|
hi = Math.log(hi)
|
|
} else {
|
|
hi = Infinity
|
|
}
|
|
hi = Math.max(hi, lo)
|
|
this.radius.bounds[0][0] = lo
|
|
this.radius.bounds[1][0] = hi
|
|
}
|
|
|
|
proto.getDistanceLimits = function(out) {
|
|
var bounds = this.radius.bounds
|
|
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.toJSON = function() {
|
|
this.recalcMatrix(this.lastT())
|
|
return {
|
|
center: this.computedCenter.slice(),
|
|
rotation: this.computedRotation.slice(),
|
|
distance: Math.log(this.computedRadius[0]),
|
|
zoomMin: this.radius.bounds[0][0],
|
|
zoomMax: this.radius.bounds[1][0]
|
|
}
|
|
}
|
|
|
|
proto.fromJSON = function(options) {
|
|
var t = this.lastT()
|
|
var c = options.center
|
|
if(c) {
|
|
this.center.set(t, c[0], c[1], c[2])
|
|
}
|
|
var r = options.rotation
|
|
if(r) {
|
|
this.rotation.set(t, r[0], r[1], r[2], r[3])
|
|
}
|
|
var d = options.distance
|
|
if(d && d > 0) {
|
|
this.radius.set(t, Math.log(d))
|
|
}
|
|
this.setDistanceLimits(options.zoomMin, options.zoomMax)
|
|
}
|
|
|
|
function createOrbitController(options) {
|
|
options = options || {}
|
|
var center = options.center || [0,0,0]
|
|
var rotation = options.rotation || [0,0,0,1]
|
|
var radius = options.radius || 1.0
|
|
|
|
center = [].slice.call(center, 0, 3)
|
|
rotation = [].slice.call(rotation, 0, 4)
|
|
normalize4(rotation, rotation)
|
|
|
|
var result = new OrbitCameraController(
|
|
rotation,
|
|
center,
|
|
Math.log(radius))
|
|
|
|
result.setDistanceLimits(options.zoomMin, options.zoomMax)
|
|
|
|
if('eye' in options || 'up' in options) {
|
|
result.lookAt(0, options.eye, options.center, options.up)
|
|
}
|
|
|
|
return result
|
|
} |