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/gl-surface3d/surface.js

1364 lines
36 KiB

'use strict'
module.exports = createSurfacePlot
var bits = require('bit-twiddle')
var createBuffer = require('gl-buffer')
var createVAO = require('gl-vao')
var createTexture = require('gl-texture2d')
var pool = require('typedarray-pool')
var colormap = require('colormap')
var ops = require('ndarray-ops')
var pack = require('ndarray-pack')
var ndarray = require('ndarray')
var surfaceNets = require('surface-nets')
var multiply = require('gl-mat4/multiply')
var invert = require('gl-mat4/invert')
var bsearch = require('binary-search-bounds')
var gradient = require('ndarray-gradient')
var shaders = require('./lib/shaders')
var createShader = shaders.createShader
var createContourShader = shaders.createContourShader
var createPickShader = shaders.createPickShader
var createPickContourShader = shaders.createPickContourShader
var SURFACE_VERTEX_SIZE = 4 * (4 + 3 + 3)
var IDENTITY = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1 ]
var QUAD = [
[0, 0],
[0, 1],
[1, 0],
[1, 1],
[1, 0],
[0, 1]
]
var PERMUTATIONS = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
]
;(function () {
for (var i = 0; i < 3; ++i) {
var p = PERMUTATIONS[i]
var u = (i + 1) % 3
var v = (i + 2) % 3
p[u + 0] = 1
p[v + 3] = 1
p[i + 6] = 1
}
})()
function SurfacePickResult (position, index, uv, level, dataCoordinate) {
this.position = position
this.index = index
this.uv = uv
this.level = level
this.dataCoordinate = dataCoordinate
}
var N_COLORS = 256
function genColormap (name) {
var x = pack([colormap({
colormap: name,
nshades: N_COLORS,
format: 'rgba'
}).map(function (c) {
return [c[0], c[1], c[2], 255 * c[3]]
})])
ops.divseq(x, 255.0)
return x
}
function SurfacePlot (
gl,
shape,
bounds,
shader,
pickShader,
coordinates,
vao,
colorMap,
contourShader,
contourPickShader,
contourBuffer,
contourVAO,
dynamicBuffer,
dynamicVAO,
objectOffset) {
this.gl = gl
this.shape = shape
this.bounds = bounds
this.objectOffset = objectOffset
this.intensityBounds = []
this._shader = shader
this._pickShader = pickShader
this._coordinateBuffer = coordinates
this._vao = vao
this._colorMap = colorMap
this._contourShader = contourShader
this._contourPickShader = contourPickShader
this._contourBuffer = contourBuffer
this._contourVAO = contourVAO
this._contourOffsets = [[], [], []]
this._contourCounts = [[], [], []]
this._vertexCount = 0
this._pickResult = new SurfacePickResult([0, 0, 0], [0, 0], [0, 0], [0, 0, 0], [0, 0, 0])
this._dynamicBuffer = dynamicBuffer
this._dynamicVAO = dynamicVAO
this._dynamicOffsets = [0, 0, 0]
this._dynamicCounts = [0, 0, 0]
this.contourWidth = [ 1, 1, 1 ]
this.contourLevels = [[1], [1], [1]]
this.contourTint = [0, 0, 0]
this.contourColor = [[0.5, 0.5, 0.5, 1], [0.5, 0.5, 0.5, 1], [0.5, 0.5, 0.5, 1]]
this.showContour = true
this.showSurface = true
this.enableHighlight = [true, true, true]
this.highlightColor = [[0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1]]
this.highlightTint = [ 1, 1, 1 ]
this.highlightLevel = [-1, -1, -1]
// Dynamic contour options
this.enableDynamic = [ true, true, true ]
this.dynamicLevel = [ NaN, NaN, NaN ]
this.dynamicColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ]
this.dynamicTint = [ 1, 1, 1 ]
this.dynamicWidth = [ 1, 1, 1 ]
this.axesBounds = [[Infinity, Infinity, Infinity], [-Infinity, -Infinity, -Infinity]]
this.surfaceProject = [ false, false, false ]
this.contourProject = [[ false, false, false ],
[ false, false, false ],
[ false, false, false ]]
this.colorBounds = [ false, false ]
// Store xyz fields, need this for picking
this._field = [
ndarray(pool.mallocFloat(1024), [0, 0]),
ndarray(pool.mallocFloat(1024), [0, 0]),
ndarray(pool.mallocFloat(1024), [0, 0]) ]
this.pickId = 1
this.clipBounds = [[-Infinity, -Infinity, -Infinity], [Infinity, Infinity, Infinity]]
this.snapToData = false
this.pixelRatio = 1
this.opacity = 1.0
this.lightPosition = [10, 10000, 0]
this.ambientLight = 0.8
this.diffuseLight = 0.8
this.specularLight = 2.0
this.roughness = 0.5
this.fresnel = 1.5
this.vertexColor = 0
this.dirty = true
}
var proto = SurfacePlot.prototype
proto.isTransparent = function () {
return this.opacity < 1
}
proto.isOpaque = function () {
if (this.opacity >= 1) {
return true
}
for (var i = 0; i < 3; ++i) {
if (this._contourCounts[i].length > 0 || this._dynamicCounts[i] > 0) {
return true
}
}
return false
}
proto.pickSlots = 1
proto.setPickBase = function (id) {
this.pickId = id
}
var ZERO_VEC = [0, 0, 0]
var PROJECT_DATA = {
showSurface: false,
showContour: false,
projections: [IDENTITY.slice(), IDENTITY.slice(), IDENTITY.slice()],
clipBounds: [
[[0, 0, 0], [0, 0, 0]],
[[0, 0, 0], [0, 0, 0]],
[[0, 0, 0], [0, 0, 0]]]
}
function computeProjectionData (camera, obj) {
var i, j, k
// Compute cube properties
var cubeAxis = (obj.axes && obj.axes.lastCubeProps.axis) || ZERO_VEC
var showSurface = obj.showSurface
var showContour = obj.showContour
for (i = 0; i < 3; ++i) {
showSurface = showSurface || obj.surfaceProject[i]
for (j = 0; j < 3; ++j) {
showContour = showContour || obj.contourProject[i][j]
}
}
for (i = 0; i < 3; ++i) {
// Construct projection onto axis
var axisSquish = PROJECT_DATA.projections[i]
for (j = 0; j < 16; ++j) {
axisSquish[j] = 0
}
for (j = 0; j < 4; ++j) {
axisSquish[5 * j] = 1
}
axisSquish[5 * i] = 0
axisSquish[12 + i] = obj.axesBounds[+(cubeAxis[i] > 0)][i]
multiply(axisSquish, camera.model, axisSquish)
var nclipBounds = PROJECT_DATA.clipBounds[i]
for (k = 0; k < 2; ++k) {
for (j = 0; j < 3; ++j) {
nclipBounds[k][j] = camera.clipBounds[k][j]
}
}
nclipBounds[0][i] = -1e8
nclipBounds[1][i] = 1e8
}
PROJECT_DATA.showSurface = showSurface
PROJECT_DATA.showContour = showContour
return PROJECT_DATA
}
var UNIFORMS = {
model: IDENTITY,
view: IDENTITY,
projection: IDENTITY,
inverseModel: IDENTITY.slice(),
lowerBound: [0, 0, 0],
upperBound: [0, 0, 0],
colorMap: 0,
clipBounds: [[0, 0, 0], [0, 0, 0]],
height: 0.0,
contourTint: 0,
contourColor: [0, 0, 0, 1],
permutation: [1, 0, 0, 0, 1, 0, 0, 0, 1],
zOffset: -1e-4,
objectOffset: [0, 0, 0],
kambient: 1,
kdiffuse: 1,
kspecular: 1,
lightPosition: [1000, 1000, 1000],
eyePosition: [0, 0, 0],
roughness: 1,
fresnel: 1,
opacity: 1,
vertexColor: 0
}
var MATRIX_INVERSE = IDENTITY.slice()
var DEFAULT_PERM = [1, 0, 0, 0, 1, 0, 0, 0, 1]
function drawCore (params, transparent) {
params = params || {}
var gl = this.gl
gl.disable(gl.CULL_FACE)
this._colorMap.bind(0)
var uniforms = UNIFORMS
uniforms.model = params.model || IDENTITY
uniforms.view = params.view || IDENTITY
uniforms.projection = params.projection || IDENTITY
uniforms.lowerBound = [this.bounds[0][0], this.bounds[0][1], this.colorBounds[0] || this.bounds[0][2]]
uniforms.upperBound = [this.bounds[1][0], this.bounds[1][1], this.colorBounds[1] || this.bounds[1][2]]
uniforms.objectOffset = this.objectOffset
uniforms.contourColor = this.contourColor[0]
uniforms.inverseModel = invert(uniforms.inverseModel, uniforms.model)
for (var i = 0; i < 2; ++i) {
var clipClamped = uniforms.clipBounds[i]
for (var j = 0; j < 3; ++j) {
clipClamped[j] = Math.min(Math.max(this.clipBounds[i][j], -1e8), 1e8)
}
}
uniforms.kambient = this.ambientLight
uniforms.kdiffuse = this.diffuseLight
uniforms.kspecular = this.specularLight
uniforms.roughness = this.roughness
uniforms.fresnel = this.fresnel
uniforms.opacity = this.opacity
uniforms.height = 0.0
uniforms.permutation = DEFAULT_PERM
uniforms.vertexColor = this.vertexColor
// Compute camera matrix inverse
var invCameraMatrix = MATRIX_INVERSE
multiply(invCameraMatrix, uniforms.view, uniforms.model)
multiply(invCameraMatrix, uniforms.projection, invCameraMatrix)
invert(invCameraMatrix, invCameraMatrix)
for (i = 0; i < 3; ++i) {
uniforms.eyePosition[i] = invCameraMatrix[12 + i] / invCameraMatrix[15]
}
var w = invCameraMatrix[15]
for (i = 0; i < 3; ++i) {
w += this.lightPosition[i] * invCameraMatrix[4 * i + 3]
}
for (i = 0; i < 3; ++i) {
var s = invCameraMatrix[12 + i]
for (j = 0; j < 3; ++j) {
s += invCameraMatrix[4 * j + i] * this.lightPosition[j]
}
uniforms.lightPosition[i] = s / w
}
var projectData = computeProjectionData(uniforms, this)
if (projectData.showSurface && (transparent === (this.opacity < 1))) {
// Set up uniforms
this._shader.bind()
this._shader.uniforms = uniforms
// Draw it
this._vao.bind()
if (this.showSurface && this._vertexCount) {
this._vao.draw(gl.TRIANGLES, this._vertexCount)
}
// Draw projections of surface
for (i = 0; i < 3; ++i) {
if (!this.surfaceProject[i] || !this.vertexCount) {
continue
}
this._shader.uniforms.model = projectData.projections[i]
this._shader.uniforms.clipBounds = projectData.clipBounds[i]
this._vao.draw(gl.TRIANGLES, this._vertexCount)
}
this._vao.unbind()
}
if (projectData.showContour && !transparent) {
var shader = this._contourShader
// Don't apply lighting to contours
uniforms.kambient = 1.0
uniforms.kdiffuse = 0.0
uniforms.kspecular = 0.0
uniforms.opacity = 1.0
shader.bind()
shader.uniforms = uniforms
// Draw contour lines
var vao = this._contourVAO
vao.bind()
// Draw contour levels
for (i = 0; i < 3; ++i) {
shader.uniforms.permutation = PERMUTATIONS[i]
gl.lineWidth(this.contourWidth[i] * this.pixelRatio)
for (j = 0; j < this.contourLevels[i].length; ++j) {
if (j === this.highlightLevel[i]) {
shader.uniforms.contourColor = this.highlightColor[i]
shader.uniforms.contourTint = this.highlightTint[i]
} else if (j === 0 || (j - 1) === this.highlightLevel[i]) {
shader.uniforms.contourColor = this.contourColor[i]
shader.uniforms.contourTint = this.contourTint[i]
}
if (!this._contourCounts[i][j]) {
continue
}
shader.uniforms.height = this.contourLevels[i][j]
vao.draw(gl.LINES, this._contourCounts[i][j], this._contourOffsets[i][j])
}
}
// Draw projections of surface
for (i = 0; i < 3; ++i) {
shader.uniforms.model = projectData.projections[i]
shader.uniforms.clipBounds = projectData.clipBounds[i]
for (j = 0; j < 3; ++j) {
if (!this.contourProject[i][j]) {
continue
}
shader.uniforms.permutation = PERMUTATIONS[j]
gl.lineWidth(this.contourWidth[j] * this.pixelRatio)
for (var k = 0; k < this.contourLevels[j].length; ++k) {
if (k === this.highlightLevel[j]) {
shader.uniforms.contourColor = this.highlightColor[j]
shader.uniforms.contourTint = this.highlightTint[j]
} else if (k === 0 || (k - 1) === this.highlightLevel[j]) {
shader.uniforms.contourColor = this.contourColor[j]
shader.uniforms.contourTint = this.contourTint[j]
}
if (!this._contourCounts[j][k]) {
continue
}
shader.uniforms.height = this.contourLevels[j][k]
vao.draw(gl.LINES, this._contourCounts[j][k], this._contourOffsets[j][k])
}
}
}
vao.unbind()
// Draw dynamic contours
vao = this._dynamicVAO
vao.bind()
// Draw contour levels
for (i = 0; i < 3; ++i) {
if (this._dynamicCounts[i] === 0) {
continue
}
shader.uniforms.model = uniforms.model
shader.uniforms.clipBounds = uniforms.clipBounds
shader.uniforms.permutation = PERMUTATIONS[i]
gl.lineWidth(this.dynamicWidth[i] * this.pixelRatio)
shader.uniforms.contourColor = this.dynamicColor[i]
shader.uniforms.contourTint = this.dynamicTint[i]
shader.uniforms.height = this.dynamicLevel[i]
vao.draw(gl.LINES, this._dynamicCounts[i], this._dynamicOffsets[i])
for (j = 0; j < 3; ++j) {
if (!this.contourProject[j][i]) {
continue
}
shader.uniforms.model = projectData.projections[j]
shader.uniforms.clipBounds = projectData.clipBounds[j]
vao.draw(gl.LINES, this._dynamicCounts[i], this._dynamicOffsets[i])
}
}
vao.unbind()
}
}
proto.draw = function (params) {
return drawCore.call(this, params, false)
}
proto.drawTransparent = function (params) {
return drawCore.call(this, params, true)
}
var PICK_UNIFORMS = {
model: IDENTITY,
view: IDENTITY,
projection: IDENTITY,
inverseModel: IDENTITY,
clipBounds: [[0, 0, 0], [0, 0, 0]],
height: 0.0,
shape: [0, 0],
pickId: 0,
lowerBound: [0, 0, 0],
upperBound: [0, 0, 0],
zOffset: 0.0,
objectOffset: [0, 0, 0],
permutation: [1, 0, 0, 0, 1, 0, 0, 0, 1],
lightPosition: [0, 0, 0],
eyePosition: [0, 0, 0]
}
proto.drawPick = function (params) {
params = params || {}
var gl = this.gl
gl.disable(gl.CULL_FACE)
var uniforms = PICK_UNIFORMS
uniforms.model = params.model || IDENTITY
uniforms.view = params.view || IDENTITY
uniforms.projection = params.projection || IDENTITY
uniforms.shape = this._field[2].shape
uniforms.pickId = this.pickId / 255.0
uniforms.lowerBound = this.bounds[0]
uniforms.upperBound = this.bounds[1]
uniforms.objectOffset = this.objectOffset
uniforms.permutation = DEFAULT_PERM
for (var i = 0; i < 2; ++i) {
var clipClamped = uniforms.clipBounds[i]
for (var j = 0; j < 3; ++j) {
clipClamped[j] = Math.min(Math.max(this.clipBounds[i][j], -1e8), 1e8)
}
}
var projectData = computeProjectionData(uniforms, this)
if (projectData.showSurface) {
// Set up uniforms
this._pickShader.bind()
this._pickShader.uniforms = uniforms
// Draw it
this._vao.bind()
this._vao.draw(gl.TRIANGLES, this._vertexCount)
// Draw projections of surface
for (i = 0; i < 3; ++i) {
if (!this.surfaceProject[i]) {
continue
}
this._pickShader.uniforms.model = projectData.projections[i]
this._pickShader.uniforms.clipBounds = projectData.clipBounds[i]
this._vao.draw(gl.TRIANGLES, this._vertexCount)
}
this._vao.unbind()
}
if (projectData.showContour) {
var shader = this._contourPickShader
shader.bind()
shader.uniforms = uniforms
var vao = this._contourVAO
vao.bind()
for (j = 0; j < 3; ++j) {
gl.lineWidth(this.contourWidth[j] * this.pixelRatio)
shader.uniforms.permutation = PERMUTATIONS[j]
for (i = 0; i < this.contourLevels[j].length; ++i) {
if (this._contourCounts[j][i]) {
shader.uniforms.height = this.contourLevels[j][i]
vao.draw(gl.LINES, this._contourCounts[j][i], this._contourOffsets[j][i])
}
}
}
// Draw projections of surface
for (i = 0; i < 3; ++i) {
shader.uniforms.model = projectData.projections[i]
shader.uniforms.clipBounds = projectData.clipBounds[i]
for (j = 0; j < 3; ++j) {
if (!this.contourProject[i][j]) {
continue
}
shader.uniforms.permutation = PERMUTATIONS[j]
gl.lineWidth(this.contourWidth[j] * this.pixelRatio)
for (var k = 0; k < this.contourLevels[j].length; ++k) {
if (this._contourCounts[j][k]) {
shader.uniforms.height = this.contourLevels[j][k]
vao.draw(gl.LINES, this._contourCounts[j][k], this._contourOffsets[j][k])
}
}
}
}
vao.unbind()
}
}
proto.pick = function (selection) {
if (!selection) {
return null
}
if (selection.id !== this.pickId) {
return null
}
var shape = this._field[2].shape
var result = this._pickResult
// Compute uv coordinate
var x = shape[0] * (selection.value[0] + (selection.value[2] >> 4) / 16.0) / 255.0
var ix = Math.floor(x)
var fx = x - ix
var y = shape[1] * (selection.value[1] + (selection.value[2] & 15) / 16.0) / 255.0
var iy = Math.floor(y)
var fy = y - iy
ix += 1
iy += 1
// Compute xyz coordinate
var pos = result.position
pos[0] = pos[1] = pos[2] = 0
for (var dx = 0; dx < 2; ++dx) {
var s = dx ? fx : 1.0 - fx
for (var dy = 0; dy < 2; ++dy) {
var t = dy ? fy : 1.0 - fy
var r = ix + dx
var c = iy + dy
var w = s * t
for (var i = 0; i < 3; ++i) {
pos[i] += this._field[i].get(r, c) * w
}
}
}
// Find closest level
var levelIndex = this._pickResult.level
for (var j = 0; j < 3; ++j) {
levelIndex[j] = bsearch.le(this.contourLevels[j], pos[j])
if (levelIndex[j] < 0) {
if (this.contourLevels[j].length > 0) {
levelIndex[j] = 0
}
} else if (levelIndex[j] < this.contourLevels[j].length - 1) {
var a = this.contourLevels[j][levelIndex[j]]
var b = this.contourLevels[j][levelIndex[j] + 1]
if (Math.abs(a - pos[j]) > Math.abs(b - pos[j])) {
levelIndex[j] += 1
}
}
}
result.index[0] = fx < 0.5 ? ix : (ix + 1)
result.index[1] = fy < 0.5 ? iy : (iy + 1)
result.uv[0] = x / shape[0]
result.uv[1] = y / shape[1]
for (i = 0; i < 3; ++i) {
result.dataCoordinate[i] = this._field[i].get(result.index[0], result.index[1])
}
return result
}
proto.padField = function(dstField, srcField) {
var srcShape = srcField.shape.slice()
var dstShape = dstField.shape.slice()
// Center
ops.assign(dstField.lo(1, 1).hi(srcShape[0], srcShape[1]), srcField)
// Edges
ops.assign(dstField.lo(1).hi(srcShape[0], 1),
srcField.hi(srcShape[0], 1))
ops.assign(dstField.lo(1, dstShape[1] - 1).hi(srcShape[0], 1),
srcField.lo(0, srcShape[1] - 1).hi(srcShape[0], 1))
ops.assign(dstField.lo(0, 1).hi(1, srcShape[1]),
srcField.hi(1))
ops.assign(dstField.lo(dstShape[0] - 1, 1).hi(1, srcShape[1]),
srcField.lo(srcShape[0] - 1))
// Corners
dstField.set(0, 0, srcField.get(0, 0))
dstField.set(0, dstShape[1] - 1, srcField.get(0, srcShape[1] - 1))
dstField.set(dstShape[0] - 1, 0, srcField.get(srcShape[0] - 1, 0))
dstField.set(dstShape[0] - 1, dstShape[1] - 1, srcField.get(srcShape[0] - 1, srcShape[1] - 1))
}
function handleArray (param, ctor) {
if (Array.isArray(param)) {
return [ ctor(param[0]), ctor(param[1]), ctor(param[2]) ]
}
return [ ctor(param), ctor(param), ctor(param) ]
}
function toColor (x) {
if (Array.isArray(x)) {
if (x.length === 3) {
return [x[0], x[1], x[2], 1]
}
return [x[0], x[1], x[2], x[3]]
}
return [0, 0, 0, 1]
}
function handleColor (param) {
if (Array.isArray(param)) {
if (Array.isArray(param)) {
return [
toColor(param[0]),
toColor(param[1]),
toColor(param[2]) ]
} else {
var c = toColor(param)
return [
c.slice(),
c.slice(),
c.slice() ]
}
}
}
proto.update = function (params) {
params = params || {}
this.objectOffset = params.objectOffset || this.objectOffset
this.dirty = true
if ('contourWidth' in params) {
this.contourWidth = handleArray(params.contourWidth, Number)
}
if ('showContour' in params) {
this.showContour = handleArray(params.showContour, Boolean)
}
if ('showSurface' in params) {
this.showSurface = !!params.showSurface
}
if ('contourTint' in params) {
this.contourTint = handleArray(params.contourTint, Boolean)
}
if ('contourColor' in params) {
this.contourColor = handleColor(params.contourColor)
}
if ('contourProject' in params) {
this.contourProject = handleArray(params.contourProject, function (x) {
return handleArray(x, Boolean)
})
}
if ('surfaceProject' in params) {
this.surfaceProject = params.surfaceProject
}
if ('dynamicColor' in params) {
this.dynamicColor = handleColor(params.dynamicColor)
}
if ('dynamicTint' in params) {
this.dynamicTint = handleArray(params.dynamicTint, Number)
}
if ('dynamicWidth' in params) {
this.dynamicWidth = handleArray(params.dynamicWidth, Number)
}
if ('opacity' in params) {
this.opacity = params.opacity
}
if ('colorBounds' in params) {
this.colorBounds = params.colorBounds
}
if ('vertexColor' in params) {
this.vertexColor = params.vertexColor ? 1 : 0;
}
var field = params.field || (params.coords && params.coords[2]) || null
var levelsChanged = false
if (!field) {
if (this._field[2].shape[0] || this._field[2].shape[2]) {
field = this._field[2].lo(1, 1).hi(this._field[2].shape[0] - 2, this._field[2].shape[1] - 2)
} else {
field = this._field[2].hi(0, 0)
}
}
// Update field
if ('field' in params || 'coords' in params) {
var fsize = (field.shape[0] + 2) * (field.shape[1] + 2)
// Resize if necessary
if (fsize > this._field[2].data.length) {
pool.freeFloat(this._field[2].data)
this._field[2].data = pool.mallocFloat(bits.nextPow2(fsize))
}
// Pad field
this._field[2] = ndarray(this._field[2].data, [field.shape[0] + 2, field.shape[1] + 2])
this.padField(this._field[2], field)
// Save shape of field
this.shape = field.shape.slice()
var shape = this.shape
// Resize coordinate fields if necessary
for (var i = 0; i < 2; ++i) {
if (this._field[2].size > this._field[i].data.length) {
pool.freeFloat(this._field[i].data)
this._field[i].data = pool.mallocFloat(this._field[2].size)
}
this._field[i] = ndarray(this._field[i].data, [shape[0] + 2, shape[1] + 2])
}
// Generate x/y coordinates
if (params.coords) {
var coords = params.coords
if (!Array.isArray(coords) || coords.length !== 3) {
throw new Error('gl-surface: invalid coordinates for x/y')
}
for (i = 0; i < 2; ++i) {
var coord = coords[i]
for (j = 0; j < 2; ++j) {
if (coord.shape[j] !== shape[j]) {
throw new Error('gl-surface: coords have incorrect shape')
}
}
this.padField(this._field[i], coord)
}
} else if (params.ticks) {
var ticks = params.ticks
if (!Array.isArray(ticks) || ticks.length !== 2) {
throw new Error('gl-surface: invalid ticks')
}
for (i = 0; i < 2; ++i) {
var tick = ticks[i]
if (Array.isArray(tick) || tick.length) {
tick = ndarray(tick)
}
if (tick.shape[0] !== shape[i]) {
throw new Error('gl-surface: invalid tick length')
}
// Make a copy view of the tick array
var tick2 = ndarray(tick.data, shape)
tick2.stride[i] = tick.stride[0]
tick2.stride[i ^ 1] = 0
// Fill in field array
this.padField(this._field[i], tick2)
}
} else {
for (i = 0; i < 2; ++i) {
var offset = [0, 0]
offset[i] = 1
this._field[i] = ndarray(this._field[i].data, [shape[0] + 2, shape[1] + 2], offset, 0)
}
this._field[0].set(0, 0, 0)
for (var j = 0; j < shape[0]; ++j) {
this._field[0].set(j + 1, 0, j)
}
this._field[0].set(shape[0] + 1, 0, shape[0] - 1)
this._field[1].set(0, 0, 0)
for (j = 0; j < shape[1]; ++j) {
this._field[1].set(0, j + 1, j)
}
this._field[1].set(0, shape[1] + 1, shape[1] - 1)
}
// Save shape
var fields = this._field
// Compute surface normals
var dfields = ndarray(pool.mallocFloat(fields[2].size * 3 * 2), [3, shape[0] + 2, shape[1] + 2, 2])
for (i = 0; i < 3; ++i) {
gradient(dfields.pick(i), fields[i], 'mirror')
}
var normals = ndarray(pool.mallocFloat(fields[2].size * 3), [shape[0] + 2, shape[1] + 2, 3])
for (i = 0; i < shape[0] + 2; ++i) {
for (j = 0; j < shape[1] + 2; ++j) {
var dxdu = dfields.get(0, i, j, 0)
var dxdv = dfields.get(0, i, j, 1)
var dydu = dfields.get(1, i, j, 0)
var dydv = dfields.get(1, i, j, 1)
var dzdu = dfields.get(2, i, j, 0)
var dzdv = dfields.get(2, i, j, 1)
var nx = dydu * dzdv - dydv * dzdu
var ny = dzdu * dxdv - dzdv * dxdu
var nz = dxdu * dydv - dxdv * dydu
var nl = Math.sqrt(nx * nx + ny * ny + nz * nz)
if (nl < 1e-8) {
nl = Math.max(Math.abs(nx), Math.abs(ny), Math.abs(nz))
if (nl < 1e-8) {
nz = 1.0
ny = nx = 0.0
nl = 1.0
} else {
nl = 1.0 / nl
}
} else {
nl = 1.0 / Math.sqrt(nl)
}
normals.set(i, j, 0, nx * nl)
normals.set(i, j, 1, ny * nl)
normals.set(i, j, 2, nz * nl)
}
}
pool.free(dfields.data)
// Initialize surface
var lo = [ Infinity, Infinity, Infinity ]
var hi = [ -Infinity, -Infinity, -Infinity ]
var lo_intensity = Infinity
var hi_intensity = -Infinity
var count = (shape[0] - 1) * (shape[1] - 1) * 6
var tverts = pool.mallocFloat(bits.nextPow2(10 * count))
var tptr = 0
var vertexCount = 0
for (i = 0; i < shape[0] - 1; ++i) {
j_loop:
for (j = 0; j < shape[1] - 1; ++j) {
// Test for NaNs
for (var dx = 0; dx < 2; ++dx) {
for (var dy = 0; dy < 2; ++dy) {
for (var k = 0; k < 3; ++k) {
var f = this._field[k].get(1 + i + dx, 1 + j + dy)
if (isNaN(f) || !isFinite(f)) {
continue j_loop
}
}
}
}
for (k = 0; k < 6; ++k) {
var r = i + QUAD[k][0]
var c = j + QUAD[k][1]
var tx = this._field[0].get(r + 1, c + 1)
var ty = this._field[1].get(r + 1, c + 1)
f = this._field[2].get(r + 1, c + 1)
nx = normals.get(r + 1, c + 1, 0)
ny = normals.get(r + 1, c + 1, 1)
nz = normals.get(r + 1, c + 1, 2)
if (params.intensity) {
vf = params.intensity.get(r, c)
}
var vf = (params.intensity) ?
params.intensity.get(r, c) :
f + this.objectOffset[2];
tverts[tptr++] = r
tverts[tptr++] = c
tverts[tptr++] = tx
tverts[tptr++] = ty
tverts[tptr++] = f
tverts[tptr++] = 0
tverts[tptr++] = vf
tverts[tptr++] = nx
tverts[tptr++] = ny
tverts[tptr++] = nz
lo[0] = Math.min(lo[0], tx + this.objectOffset[0])
lo[1] = Math.min(lo[1], ty + this.objectOffset[1])
lo[2] = Math.min(lo[2], f + this.objectOffset[2])
lo_intensity = Math.min(lo_intensity, vf)
hi[0] = Math.max(hi[0], tx + this.objectOffset[0])
hi[1] = Math.max(hi[1], ty + this.objectOffset[1])
hi[2] = Math.max(hi[2], f + this.objectOffset[2])
hi_intensity = Math.max(hi_intensity, vf)
vertexCount += 1
}
}
}
if (params.intensityBounds) {
lo_intensity = +params.intensityBounds[0]
hi_intensity = +params.intensityBounds[1]
}
// Scale all vertex intensities
for (i = 6; i < tptr; i += 10) {
tverts[i] = (tverts[i] - lo_intensity) / (hi_intensity - lo_intensity)
}
this._vertexCount = vertexCount
this._coordinateBuffer.update(tverts.subarray(0, tptr))
pool.freeFloat(tverts)
pool.free(normals.data)
// Update bounds
this.bounds = [lo, hi]
// Save intensity
this.intensity = params.intensity || this._field[2]
if(this.intensityBounds[0] !== lo_intensity || this.intensityBounds[1] !== hi_intensity) {
levelsChanged = true
}
// Save intensity bound
this.intensityBounds = [lo_intensity, hi_intensity]
}
// Update level crossings
if ('levels' in params) {
var levels = params.levels
if (!Array.isArray(levels[0])) {
levels = [ [], [], levels ]
} else {
levels = levels.slice()
}
for (i = 0; i < 3; ++i) {
levels[i] = levels[i].slice()
levels[i].sort(function (a, b) {
return a - b
})
}
for (i = 0; i < 3; ++i) {
for (j = 0; j < levels[i].length; ++j) {
levels[i][j] -= this.objectOffset[i]
}
}
change_test:
for (i = 0; i < 3; ++i) {
if (levels[i].length !== this.contourLevels[i].length) {
levelsChanged = true
break
}
for (j = 0; j < levels[i].length; ++j) {
if (levels[i][j] !== this.contourLevels[i][j]) {
levelsChanged = true
break change_test
}
}
}
this.contourLevels = levels
}
if (levelsChanged) {
fields = this._field
shape = this.shape
// Update contour lines
var contourVerts = []
for (var dim = 0; dim < 3; ++dim) {
var contourLevel = this.contourLevels[dim]
var levelOffsets = []
var levelCounts = []
var parts = [0, 0, 0]
for (i = 0; i < contourLevel.length; ++i) {
var graph = surfaceNets(this._field[dim], contourLevel[i])
levelOffsets.push((contourVerts.length / 5) | 0)
vertexCount = 0
edge_loop:
for (j = 0; j < graph.cells.length; ++j) {
var e = graph.cells[j]
for (k = 0; k < 2; ++k) {
var p = graph.positions[e[k]]
var x = p[0]
var ix = Math.floor(x) | 0
var fx = x - ix
var y = p[1]
var iy = Math.floor(y) | 0
var fy = y - iy
var hole = false
axis_loop:
for (var axis = 0; axis < 3; ++axis) {
parts[axis] = 0.0
var iu = (dim + axis + 1) % 3
for (dx = 0; dx < 2; ++dx) {
var s = dx ? fx : 1.0 - fx
r = Math.min(Math.max(ix + dx, 0), shape[0]) | 0
for (dy = 0; dy < 2; ++dy) {
var t = dy ? fy : 1.0 - fy
c = Math.min(Math.max(iy + dy, 0), shape[1]) | 0
if (axis < 2) {
f = this._field[iu].get(r, c)
} else {
f = (this.intensity.get(r, c) - this.intensityBounds[0]) / (this.intensityBounds[1] - this.intensityBounds[0])
}
if (!isFinite(f) || isNaN(f)) {
hole = true
break axis_loop
}
var w = s * t
parts[axis] += w * f
}
}
}
if (!hole) {
contourVerts.push(
parts[0],
parts[1],
p[0],
p[1],
parts[2]
)
vertexCount += 1
} else {
if (k > 0) {
// If we already added first edge, pop off verts
for (var l = 0; l < 5; ++l) {
contourVerts.pop()
}
vertexCount -= 1
}
continue edge_loop
}
}
}
levelCounts.push(vertexCount)
}
// Store results
this._contourOffsets[dim] = levelOffsets
this._contourCounts[dim] = levelCounts
}
var floatBuffer = pool.mallocFloat(contourVerts.length)
for (i = 0; i < contourVerts.length; ++i) {
floatBuffer[i] = contourVerts[i]
}
this._contourBuffer.update(floatBuffer)
pool.freeFloat(floatBuffer)
}
if (params.colormap) {
this._colorMap.setPixels(genColormap(params.colormap))
}
}
proto.dispose = function () {
this._shader.dispose()
this._vao.dispose()
this._coordinateBuffer.dispose()
this._colorMap.dispose()
this._contourBuffer.dispose()
this._contourVAO.dispose()
this._contourShader.dispose()
this._contourPickShader.dispose()
this._dynamicBuffer.dispose()
this._dynamicVAO.dispose()
for (var i = 0; i < 3; ++i) {
pool.freeFloat(this._field[i].data)
}
}
proto.highlight = function (selection) {
var i
if (!selection) {
this._dynamicCounts = [0, 0, 0]
this.dyanamicLevel = [NaN, NaN, NaN]
this.highlightLevel = [-1, -1, -1]
return
}
for (i = 0; i < 3; ++i) {
if (this.enableHighlight[i]) {
this.highlightLevel[i] = selection.level[i]
} else {
this.highlightLevel[i] = -1
}
}
var levels
if (this.snapToData) {
levels = selection.dataCoordinate
} else {
levels = selection.position
}
for (i = 0; i < 3; ++i) {
levels[i] -= this.objectOffset[i]
}
if ((!this.enableDynamic[0] || levels[0] === this.dynamicLevel[0]) &&
(!this.enableDynamic[1] || levels[1] === this.dynamicLevel[1]) &&
(!this.enableDynamic[2] || levels[2] === this.dynamicLevel[2])) {
return
}
var vertexCount = 0
var shape = this.shape
var scratchBuffer = pool.mallocFloat(12 * shape[0] * shape[1])
for (var d = 0; d < 3; ++d) {
if (!this.enableDynamic[d]) {
this.dynamicLevel[d] = NaN
this._dynamicCounts[d] = 0
continue
}
this.dynamicLevel[d] = levels[d]
var u = (d + 1) % 3
var v = (d + 2) % 3
var f = this._field[d]
var g = this._field[u]
var h = this._field[v]
var graph = surfaceNets(f, levels[d])
var edges = graph.cells
var positions = graph.positions
this._dynamicOffsets[d] = vertexCount
for (i = 0; i < edges.length; ++i) {
var e = edges[i]
for (var j = 0; j < 2; ++j) {
var p = positions[e[j]]
var x = +p[0]
var ix = x | 0
var jx = Math.min(ix + 1, shape[0]) | 0
var fx = x - ix
var hx = 1.0 - fx
var y = +p[1]
var iy = y | 0
var jy = Math.min(iy + 1, shape[1]) | 0
var fy = y - iy
var hy = 1.0 - fy
var w00 = hx * hy
var w01 = hx * fy
var w10 = fx * hy
var w11 = fx * fy
var cu = w00 * g.get(ix, iy) +
w01 * g.get(ix, jy) +
w10 * g.get(jx, iy) +
w11 * g.get(jx, jy)
var cv = w00 * h.get(ix, iy) +
w01 * h.get(ix, jy) +
w10 * h.get(jx, iy) +
w11 * h.get(jx, jy)
if (isNaN(cu) || isNaN(cv)) {
if (j) {
vertexCount -= 1
}
break
}
scratchBuffer[2 * vertexCount + 0] = cu
scratchBuffer[2 * vertexCount + 1] = cv
vertexCount += 1
}
}
this._dynamicCounts[d] = vertexCount - this._dynamicOffsets[d]
}
this._dynamicBuffer.update(scratchBuffer.subarray(0, 2 * vertexCount))
pool.freeFloat(scratchBuffer)
}
function createSurfacePlot (params) {
var gl = params.gl
var shader = createShader(gl)
var pickShader = createPickShader(gl)
var contourShader = createContourShader(gl)
var contourPickShader = createPickContourShader(gl)
var coordinateBuffer = createBuffer(gl)
var vao = createVAO(gl, [
{ buffer: coordinateBuffer,
size: 4,
stride: SURFACE_VERTEX_SIZE,
offset: 0
},
{ buffer: coordinateBuffer,
size: 3,
stride: SURFACE_VERTEX_SIZE,
offset: 16
},
{
buffer: coordinateBuffer,
size: 3,
stride: SURFACE_VERTEX_SIZE,
offset: 28
}
])
var contourBuffer = createBuffer(gl)
var contourVAO = createVAO(gl, [
{
buffer: contourBuffer,
size: 4,
stride: 20,
offset: 0
},
{
buffer: contourBuffer,
size: 1,
stride: 20,
offset: 16
}
])
var dynamicBuffer = createBuffer(gl)
var dynamicVAO = createVAO(gl, [
{
buffer: dynamicBuffer,
size: 2,
type: gl.FLOAT
}])
var cmap = createTexture(gl, 1, N_COLORS, gl.RGBA, gl.UNSIGNED_BYTE)
cmap.minFilter = gl.LINEAR
cmap.magFilter = gl.LINEAR
var surface = new SurfacePlot(
gl,
[0, 0], // shape
[[0, 0, 0], [0, 0, 0]], // bounds
shader,
pickShader,
coordinateBuffer,
vao,
cmap,
contourShader,
contourPickShader,
contourBuffer,
contourVAO,
dynamicBuffer,
dynamicVAO,
[0, 0, 0] // objectOffset
)
var nparams = {
levels: [[], [], []]
}
for (var id in params) {
nparams[id] = params[id]
}
nparams.colormap = nparams.colormap || 'jet'
surface.update(nparams)
return surface
}