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-streamtube3d/streamtube.js

558 lines
13 KiB

"use strict";
var vec3 = require('gl-vec3');
var vec4 = require('gl-vec4');
var GRID_TYPES = ['xyz', 'xzy', 'yxz', 'yzx', 'zxy', 'zyx'];
var streamToTube = function(stream, maxDivergence, minDistance, maxNorm) {
var points = stream.points;
var velocities = stream.velocities;
var divergences = stream.divergences;
var verts = [];
var faces = [];
var vectors = [];
var previousVerts = [];
var currentVerts = [];
var intensities = [];
var previousIntensity = 0;
var currentIntensity = 0;
var currentVector = vec4.create();
var previousVector = vec4.create();
var facets = 8;
for (var i = 0; i < points.length; i++) {
var p = points[i];
var fwd = velocities[i];
var r = divergences[i];
if (maxDivergence === 0) {
r = minDistance * 0.05;
}
currentIntensity = vec3.length(fwd) / maxNorm;
currentVector = vec4.create();
vec3.copy(currentVector, fwd);
currentVector[3] = r;
for (var a = 0; a < facets; a++) {
currentVerts[a] = [p[0], p[1], p[2], a];
}
if (previousVerts.length > 0) {
for (var a = 0; a < facets; a++) {
var a1 = (a+1) % facets;
verts.push(
previousVerts[a],
currentVerts[a],
currentVerts[a1],
currentVerts[a1],
previousVerts[a1],
previousVerts[a]
);
vectors.push(
previousVector,
currentVector,
currentVector,
currentVector,
previousVector,
previousVector
);
intensities.push(
previousIntensity,
currentIntensity,
currentIntensity,
currentIntensity,
previousIntensity,
previousIntensity
);
var len = verts.length;
faces.push(
[len-6, len-5, len-4],
[len-3, len-2, len-1]
);
}
}
var tmp1 = previousVerts;
previousVerts = currentVerts;
currentVerts = tmp1;
var tmp2 = previousVector;
previousVector = currentVector;
currentVector = tmp2;
var tmp3 = previousIntensity;
previousIntensity = currentIntensity;
currentIntensity = tmp3;
}
return {
positions: verts,
cells: faces,
vectors: vectors,
vertexIntensity: intensities
};
};
var createTubes = function(streams, colormap, maxDivergence, minDistance) {
var maxNorm = 0;
for (var i=0; i<streams.length; i++) {
var velocities = streams[i].velocities;
for (var j=0; j<velocities.length; j++) {
maxNorm = Math.max(maxNorm,
vec3.length(velocities[j])
);
}
}
var tubes = streams.map(function(s) {
return streamToTube(s, maxDivergence, minDistance, maxNorm);
});
var positions = [];
var cells = [];
var vectors = [];
var vertexIntensity = [];
for (var i=0; i < tubes.length; i++) {
var tube = tubes[i];
var offset = positions.length;
positions = positions.concat(tube.positions);
vectors = vectors.concat(tube.vectors);
vertexIntensity = vertexIntensity.concat(tube.vertexIntensity);
for (var j=0; j<tube.cells.length; j++) {
var cell = tube.cells[j];
var newCell = [];
cells.push(newCell);
for (var k=0; k<cell.length; k++) {
newCell.push(cell[k] + offset);
}
}
}
return {
positions: positions,
cells: cells,
vectors: vectors,
vertexIntensity: vertexIntensity,
colormap: colormap
};
};
var findLastSmallerIndex = function(points, v) {
var len = points.length;
var i;
for (i=0; i<len; i++) {
var p = points[i];
if (p === v) return i;
else if (p > v) return i-1;
}
return i;
};
var clamp = function(v, min, max) {
return v < min ? min : (v > max ? max : v);
};
var sampleMeshgrid = function(point, vectorField, gridInfo) {
var vectors = vectorField.vectors;
var meshgrid = vectorField.meshgrid;
var x = point[0];
var y = point[1];
var z = point[2];
var w = meshgrid[0].length;
var h = meshgrid[1].length;
var d = meshgrid[2].length;
// Find the index of the nearest smaller value in the meshgrid for each coordinate of (x,y,z).
// The nearest smaller value index for x is the index x0 such that
// meshgrid[0][x0] < x and for all x1 > x0, meshgrid[0][x1] >= x.
var x0 = findLastSmallerIndex(meshgrid[0], x);
var y0 = findLastSmallerIndex(meshgrid[1], y);
var z0 = findLastSmallerIndex(meshgrid[2], z);
// Get the nearest larger meshgrid value indices.
// From the above "nearest smaller value", we know that
// meshgrid[0][x0] < x
// meshgrid[0][x0+1] >= x
var x1 = x0 + 1;
var y1 = y0 + 1;
var z1 = z0 + 1;
x0 = clamp(x0, 0, w-1);
x1 = clamp(x1, 0, w-1);
y0 = clamp(y0, 0, h-1);
y1 = clamp(y1, 0, h-1);
z0 = clamp(z0, 0, d-1);
z1 = clamp(z1, 0, d-1);
// Reject points outside the meshgrid, return a zero vector.
if (x0 < 0 || y0 < 0 || z0 < 0 || x1 > w-1 || y1 > h-1 || z1 > d-1) {
return vec3.create();
}
// Normalize point coordinates to 0..1 scaling factor between x0 and x1.
var mX0 = meshgrid[0][x0];
var mX1 = meshgrid[0][x1];
var mY0 = meshgrid[1][y0];
var mY1 = meshgrid[1][y1];
var mZ0 = meshgrid[2][z0];
var mZ1 = meshgrid[2][z1];
var xf = (x - mX0) / (mX1 - mX0);
var yf = (y - mY0) / (mY1 - mY0);
var zf = (z - mZ0) / (mZ1 - mZ0);
if (!isFinite(xf)) xf = 0.5;
if (!isFinite(yf)) yf = 0.5;
if (!isFinite(zf)) zf = 0.5;
var x0off;
var x1off;
var y0off;
var y1off;
var z0off;
var z1off;
if(gridInfo.reversedX) {
x0 = w - 1 - x0;
x1 = w - 1 - x1;
}
if(gridInfo.reversedY) {
y0 = h - 1 - y0;
y1 = h - 1 - y1;
}
if(gridInfo.reversedZ) {
z0 = d - 1 - z0;
z1 = d - 1 - z1;
}
switch(gridInfo.filled) {
case 5: // 'zyx'
z0off = z0;
z1off = z1;
y0off = y0*d;
y1off = y1*d;
x0off = x0*d*h;
x1off = x1*d*h;
break;
case 4: // 'zxy'
z0off = z0;
z1off = z1;
x0off = x0*d;
x1off = x1*d;
y0off = y0*d*w;
y1off = y1*d*w;
break;
case 3: // 'yzx'
y0off = y0;
y1off = y1;
z0off = z0*h;
z1off = z1*h;
x0off = x0*h*d;
x1off = x1*h*d;
break;
case 2: // 'yxz'
y0off = y0;
y1off = y1;
x0off = x0*h;
x1off = x1*h;
z0off = z0*h*w;
z1off = z1*h*w;
break;
case 1: // 'xzy'
x0off = x0;
x1off = x1;
z0off = z0*w;
z1off = z1*w;
y0off = y0*w*d;
y1off = y1*w*d;
break;
default: // case 0: // 'xyz'
x0off = x0;
x1off = x1;
y0off = y0*w;
y1off = y1*w;
z0off = z0*w*h;
z1off = z1*w*h;
break;
}
// Sample data vectors around the (x,y,z) point.
var v000 = vectors[x0off + y0off + z0off];
var v001 = vectors[x0off + y0off + z1off];
var v010 = vectors[x0off + y1off + z0off];
var v011 = vectors[x0off + y1off + z1off];
var v100 = vectors[x1off + y0off + z0off];
var v101 = vectors[x1off + y0off + z1off];
var v110 = vectors[x1off + y1off + z0off];
var v111 = vectors[x1off + y1off + z1off];
var c00 = vec3.create();
var c01 = vec3.create();
var c10 = vec3.create();
var c11 = vec3.create();
vec3.lerp(c00, v000, v100, xf);
vec3.lerp(c01, v001, v101, xf);
vec3.lerp(c10, v010, v110, xf);
vec3.lerp(c11, v011, v111, xf);
var c0 = vec3.create();
var c1 = vec3.create();
vec3.lerp(c0, c00, c10, yf);
vec3.lerp(c1, c01, c11, yf);
var c = vec3.create();
vec3.lerp(c, c0, c1, zf);
return c;
};
var vabs = function(dst, v) {
var x = v[0];
var y = v[1];
var z = v[2];
dst[0] = x < 0 ? -x : x;
dst[1] = y < 0 ? -y : y;
dst[2] = z < 0 ? -z : z;
return dst;
};
var findMinSeparation = function(xs) {
var minSeparation = Infinity;
xs.sort(function(a, b) { return a - b; });
var len = xs.length;
for (var i=1; i<len; i++) {
var d = Math.abs(xs[i] - xs[i-1]);
if (d < minSeparation) {
minSeparation = d;
}
}
return minSeparation;
};
// Finds the minimum per-component distance in positions.
//
var calculateMinPositionDistance = function(positions) {
var xs = [], ys = [], zs = [];
var xi = {}, yi = {}, zi = {};
var len = positions.length;
for (var i=0; i<len; i++) {
var p = positions[i];
var x = p[0], y = p[1], z = p[2];
// Split the positions array into arrays of unique component values.
//
// Why go through the trouble of using a uniqueness hash table vs
// sort and uniq:
//
// Suppose you've got a million positions in a 100x100x100 grid.
//
// Using a uniqueness hash table, you're doing 1M array reads,
// 3M hash table lookups from 100-element hashes, 300 hash table inserts, then
// sorting three 100-element arrays and iterating over them.
// Roughly, 1M + 3M * ln(100) + 300 * ln(100/2) + 3 * 100 * ln(100) + 3 * 100 =
// 1M + 13.8M + 0.0012M + 0.0014M + 0.0003M
// =~ 15M
//
// Sort and uniq solution would do 1M array reads, 3M array inserts,
// sort three 1M-element arrays and iterate over them.
// Roughly, 1M + 3M + 3 * 1M * ln(1M) + 3 * 1M =
// 1M + 3M + 41.4M + 3M
// =~ 48.4M
//
// Guessing that a hard-coded sort & uniq would be faster due to not having
// to run a hashing function on everything. More memory usage though
// (bunch of small hash tables vs. duplicating the input array.)
//
// In JS-land, who knows. Maybe xi[x] casts x to string and destroys perf,
// maybe numeric keys get special-cased, maybe the object lookups run at near O(1)-speeds.
// Maybe the sorting comparison function is expensive to call, maybe it gets inlined or special-cased.
//
// ... You're probably not going to call this with more than 10k positions anyhow, so this is very academic.
//
if (!xi[x]) {
xs.push(x);
xi[x] = true;
}
if (!yi[y]) {
ys.push(y);
yi[y] = true;
}
if (!zi[z]) {
zs.push(z);
zi[z] = true;
}
}
var xSep = findMinSeparation(xs);
var ySep = findMinSeparation(ys);
var zSep = findMinSeparation(zs);
var minSeparation = Math.min(xSep, ySep, zSep);
return isFinite(minSeparation) ? minSeparation : 1;
};
module.exports = function(vectorField, bounds) {
var positions = vectorField.startingPositions;
var maxLength = vectorField.maxLength || 1000;
var tubeSize = vectorField.tubeSize || 1;
var absoluteTubeSize = vectorField.absoluteTubeSize;
var gridFill = vectorField.gridFill || '+x+y+z';
var gridInfo = {};
if(gridFill.indexOf('-x') !== -1) { gridInfo.reversedX = true; }
if(gridFill.indexOf('-y') !== -1) { gridInfo.reversedY = true; }
if(gridFill.indexOf('-z') !== -1) { gridInfo.reversedZ = true; }
gridInfo.filled = GRID_TYPES.indexOf(gridFill.replace(/-/g, '').replace(/\+/g, ''));
var getVelocity = vectorField.getVelocity || function(p) {
return sampleMeshgrid(p, vectorField, gridInfo);
};
var getDivergence = vectorField.getDivergence || function(p, v0) {
var dp = vec3.create();
var e = 0.0001;
vec3.add(dp, p, [e, 0, 0]);
var vx = getVelocity(dp);
vec3.subtract(vx, vx, v0);
vec3.scale(vx, vx, 1/e);
vec3.add(dp, p, [0, e, 0]);
var vy = getVelocity(dp);
vec3.subtract(vy, vy, v0);
vec3.scale(vy, vy, 1/e);
vec3.add(dp, p, [0, 0, e]);
var vz = getVelocity(dp);
vec3.subtract(vz, vz, v0);
vec3.scale(vz, vz, 1/e);
vec3.add(dp, vx, vy);
vec3.add(dp, dp, vz);
return dp;
};
var streams = [];
var minX = bounds[0][0], minY = bounds[0][1], minZ = bounds[0][2];
var maxX = bounds[1][0], maxY = bounds[1][1], maxZ = bounds[1][2];
var inBounds = function(p) {
var x = p[0];
var y = p[1];
var z = p[2];
return !(
x < minX || x > maxX ||
y < minY || y > maxY ||
z < minZ || z > maxZ
);
};
var boundsSize = vec3.distance(bounds[0], bounds[1]);
var maxStepSize = 10 * boundsSize / maxLength;
var maxStepSizeSq = maxStepSize * maxStepSize;
var minDistance = 1;
var maxDivergence = 0; // For component-wise divergence vec3.create();
// In case we need to do component-wise divergence visualization
// var tmp = vec3.create();
var len = positions.length;
if (len > 1) {
minDistance = calculateMinPositionDistance(positions);
}
for (var i = 0; i < len; i++) {
var p = vec3.create();
vec3.copy(p, positions[i]);
var stream = [p];
var velocities = [];
var v = getVelocity(p);
var op = p;
velocities.push(v);
var divergences = [];
var dv = getDivergence(p, v);
var dvLength = vec3.length(dv);
if (isFinite(dvLength) && dvLength > maxDivergence) {
maxDivergence = dvLength;
}
// In case we need to do component-wise divergence visualization
// vec3.max(maxDivergence, maxDivergence, vabs(tmp, dv));
divergences.push(dvLength);
streams.push({points: stream, velocities: velocities, divergences: divergences});
var j = 0;
while (j < maxLength * 100 && stream.length < maxLength && inBounds(p)) {
j++;
var np = vec3.clone(v);
var sqLen = vec3.squaredLength(np);
if (sqLen === 0) {
break;
} else if (sqLen > maxStepSizeSq) {
vec3.scale(np, np, maxStepSize / Math.sqrt(sqLen));
}
vec3.add(np, np, p);
v = getVelocity(np);
if (vec3.squaredDistance(op, np) - maxStepSizeSq > -0.0001 * maxStepSizeSq) {
stream.push(np);
op = np;
velocities.push(v);
var dv = getDivergence(np, v);
var dvLength = vec3.length(dv);
if (isFinite(dvLength) && dvLength > maxDivergence) {
maxDivergence = dvLength;
}
// In case we need to do component-wise divergence visualization
//vec3.max(maxDivergence, maxDivergence, vabs(tmp, dv));
divergences.push(dvLength);
}
p = np;
}
}
var tubes = createTubes(streams, vectorField.colormap, maxDivergence, minDistance);
if (absoluteTubeSize) {
tubes.tubeScale = absoluteTubeSize;
} else {
// Avoid division by zero.
if (maxDivergence === 0) {
maxDivergence = 1;
}
tubes.tubeScale = tubeSize * 0.5 * minDistance / maxDivergence;
}
return tubes;
};
var shaders = require('./lib/shaders');
var createMesh = require('gl-cone3d').createMesh;
module.exports.createTubeMesh = function(gl, params) {
return createMesh(gl, params, {
shaders: shaders,
traceType: 'streamtube'
});
}