"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 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 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' }); }