UNPKG

@thewtex/vtk.js-esm

Version:

Visualization Toolkit for the Web

291 lines (226 loc) 14.9 kB
import { newInstance as newInstance$1, vtkErrorMacro as vtkErrorMacro$1 } from '../../macro.js'; import vtkWebGPUPolyDataMapper from './PolyDataMapper.js'; import vtkWebGPUBufferManager from './BufferManager.js'; import vtkWebGPUShaderCache from './ShaderCache.js'; import { registerOverride } from './ViewNodeFactory.js'; var BufferUsage = vtkWebGPUBufferManager.BufferUsage, PrimitiveTypes = vtkWebGPUBufferManager.PrimitiveTypes; var vtkErrorMacro = vtkErrorMacro$1; // Vertices // 013 - 032 - 324 - 453 // // _.4---_.5 // .-* .-* // 2-----3 // | /| // | / | // | / | // | / | // |/ | // 0-----1 // // coord for each points // 0: 000 // 1: 100 // 2: 001 // 3: 101 // 4: 011 // 5: 111 var vtkWebGPUStickMapperVS = "\n//VTK::Renderer::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::Color::Dec\n\n//VTK::IOStructs::Dec\n\n[[stage(vertex)]]\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n let offsetsArray: array<vec3<f32>, 12> = array<vec3<f32>, 12>(\n vec3<f32>(-1.0, -1.0, -1.0),\n vec3<f32>(1.0, -1.0, -1.0),\n vec3<f32>(1.0, -1.0, 1.0),\n\n vec3<f32>(-1.0, -1.0, -1.0),\n vec3<f32>(1.0, -1.0, 1.0),\n vec3<f32>(-1.0, -1.0, 1.0),\n\n vec3<f32>(-1.0, -1.0, 1.0),\n vec3<f32>(1.0, -1.0, 1.0),\n vec3<f32>(1.0, 1.0, 1.0),\n\n vec3<f32>(-1.0, -1.0, 1.0),\n vec3<f32>(1.0, 1.0, 1.0),\n vec3<f32>(-1.0, 1.0, 1.0)\n );\n\n var output : vertexOutput;\n\n var vertexVC: vec4<f32> = rendererUBO.SCVCMatrix * mapperUBO.BCSCMatrix * vec4<f32>(vertexBC.x, vertexBC.y, vertexBC.z, 1.0);\n\n //VTK::Color::Impl\n\n // compute the projected vertex position\n output.centerVC = vertexVC.xyz;\n output.radiusVC = radiusMC;\n output.lengthVC = length(orientMC);\n output.orientVC = (rendererUBO.WCVCNormals * vec4<f32>(normalize(orientMC), 0.0)).xyz;\n\n // make sure it is pointing out of the screen\n if (output.orientVC.z < 0.0)\n {\n output.orientVC = -output.orientVC;\n }\n\n // make the basis\n var xbase: vec3<f32>;\n var ybase: vec3<f32>;\n var dir: vec3<f32> = vec3<f32>(0.0,0.0,1.0);\n if (rendererUBO.cameraParallel == 0u)\n {\n dir = normalize(-vertexVC.xyz);\n }\n if (abs(dot(dir,output.orientVC)) == 1.0)\n {\n xbase = normalize(cross(vec3<f32>(0.0,1.0,0.0),output.orientVC));\n ybase = cross(xbase,output.orientVC);\n }\n else\n {\n xbase = normalize(cross(output.orientVC,dir));\n ybase = cross(output.orientVC,xbase);\n }\n\n\n var vertIdx: u32 = input.vertexIndex % 12u;\n var offsets: vec3<f32> = offsetsArray[vertIdx];\n\n vertexVC = vec4<f32>(vertexVC.xyz +\n output.radiusVC * offsets.x * xbase +\n output.radiusVC * offsets.y * ybase +\n 0.5 * output.lengthVC * offsets.z * output.orientVC, 1.0);\n\n output.vertexVC = vertexVC;\n\n //VTK::Position::Impl\n\n return output;\n}\n"; // ---------------------------------------------------------------------------- // vtkWebGPUStickMapper methods // ---------------------------------------------------------------------------- function vtkWebGPUStickMapper(publicAPI, model) { // Set our className model.classHierarchy.push('vtkWebGPUStickMapper'); publicAPI.replaceShaderNormal = function (hash, pipeline, vertexInput) { var vDesc = pipeline.getShaderDescription('vertex'); vDesc.addOutput('vec4<f32>', 'vertexVC'); vDesc.addOutput('vec3<f32>', 'centerVC'); vDesc.addOutput('vec3<f32>', 'orientVC'); vDesc.addOutput('f32', 'radiusVC'); vDesc.addOutput('f32', 'lengthVC'); vDesc.addBuiltinInput('u32', '[[builtin(vertex_index)]] vertexIndex'); var fDesc = pipeline.getShaderDescription('fragment'); fDesc.addBuiltinOutput('f32', '[[builtin(frag_depth)]] fragDepth'); var stickFrag = "\n // compute the eye position and unit direction\n var vertexVC: vec4<f32>;\n var EyePos: vec3<f32>;\n var EyeDir: vec3<f32>;\n\n if (rendererUBO.cameraParallel != 0u)\n {\n EyePos = vec3<f32>(input.vertexVC.x, input.vertexVC.y, input.vertexVC.z + 3.0*input.radiusVC);\n EyeDir = vec3<f32>(0.0, 0.0, -1.0);\n }\n else\n {\n EyeDir = input.vertexVC.xyz;\n EyePos = vec3<f32>(0.0,0.0,0.0);\n var lengthED: f32 = length(EyeDir);\n EyeDir = normalize(EyeDir);\n // we adjust the EyePos to be closer if it is too far away\n // to prevent floating point precision noise\n if (lengthED > input.radiusVC*3.0)\n {\n EyePos = input.vertexVC.xyz - EyeDir*3.0*input.radiusVC;\n }\n }\n // translate to Sphere center\n EyePos = EyePos - input.centerVC;\n\n // rotate to new basis\n // base1, base2, orientVC\n var base1: vec3<f32>;\n if (abs(input.orientVC.z) < 0.99)\n {\n base1 = normalize(cross(input.orientVC,vec3<f32>(0.0,0.0,1.0)));\n }\n else\n {\n base1 = normalize(cross(input.orientVC,vec3<f32>(0.0,1.0,0.0)));\n }\n var base2: vec3<f32> = cross(input.orientVC,base1);\n EyePos = vec3<f32>(dot(EyePos,base1),dot(EyePos,base2),dot(EyePos,input.orientVC));\n EyeDir = vec3<f32>(dot(EyeDir,base1),dot(EyeDir,base2),dot(EyeDir,input.orientVC));\n\n // scale to radius 1.0\n EyePos = EyePos * (1.0 / input.radiusVC);\n\n // find the intersection\n var a: f32 = EyeDir.x*EyeDir.x + EyeDir.y*EyeDir.y;\n var b: f32 = 2.0*(EyePos.x*EyeDir.x + EyePos.y*EyeDir.y);\n var c: f32 = EyePos.x*EyePos.x + EyePos.y*EyePos.y - 1.0;\n var d: f32 = b*b - 4.0*a*c;\n var normal: vec3<f32> = vec3<f32>(0.0,0.0,1.0);\n if (d < 0.0) { discard; }\n else\n {\n var t: f32 = (-b - sqrt(d))*(0.5 / a);\n var tz: f32 = EyePos.z + t*EyeDir.z;\n var iPoint: vec3<f32> = EyePos + t*EyeDir;\n if (abs(iPoint.z)*input.radiusVC > input.lengthVC*0.5)\n {\n // test for end cap\n var t2: f32 = (-b + sqrt(d))*(0.5 / a);\n var tz2: f32 = EyePos.z + t2*EyeDir.z;\n if (tz2*input.radiusVC > input.lengthVC*0.5 || tz*input.radiusVC < -0.5*input.lengthVC) { discard; }\n else\n {\n normal = input.orientVC;\n var t3: f32 = (input.lengthVC*0.5/input.radiusVC - EyePos.z)/EyeDir.z;\n iPoint = EyePos + t3*EyeDir;\n vertexVC = vec4<f32>(input.radiusVC*(iPoint.x*base1 + iPoint.y*base2 + iPoint.z*input.orientVC) + input.centerVC, 1.0);\n }\n }\n else\n {\n // The normal is the iPoint.xy rotated back into VC\n normal = iPoint.x*base1 + iPoint.y*base2;\n // rescale rerotate and translate\n vertexVC = vec4<f32>(input.radiusVC*(normal + iPoint.z*input.orientVC) + input.centerVC, 1.0);\n }\n // compute the pixel's depth\n var pos: vec4<f32> = rendererUBO.VCPCMatrix * vertexVC;\n output.fragDepth = pos.z / pos.w;\n }\n "; var code = fDesc.getCode(); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Normal::Impl', [stickFrag]).result; fDesc.setCode(code); }; publicAPI.replaceShaderPosition = function (hash, pipeline, vertexInput) { var vDesc = pipeline.getShaderDescription('vertex'); vDesc.addBuiltinOutput('vec4<f32>', '[[builtin(position)]] Position'); var code = vDesc.getCode(); code = vtkWebGPUShaderCache.substitute(code, '//VTK::Position::Impl', [' output.Position = rendererUBO.VCPCMatrix*vertexVC;']).result; vDesc.setCode(code); }; // compute a unique hash for a pipeline, this needs to be unique enough to // capture any pipeline code changes (which includes shader changes) // or vertex input changes/ bind groups/ etc publicAPI.computePipelineHash = function (vertexInput) { var pipelineHash = 'stm'; if (vertexInput.hasAttribute("colorVI")) { pipelineHash += "c"; } pipelineHash += model.renderEncoder.getPipelineHash(); return pipelineHash; }; // was originally buildIBOs() but not using IBOs right now publicAPI.buildPrimitives = function () { var poly = model.currentInput; var device = model.WebGPURenderWindow.getDevice(); model.renderable.mapScalars(poly, 1.0); // handle triangles var i = PrimitiveTypes.Triangles; var points = poly.getPoints(); var pointData = poly.getPointData(); var numPoints = points.getNumberOfPoints(); var pointArray = points.getData(); var primHelper = model.primitives[i]; primHelper.setNumberOfInstances(numPoints); primHelper.setNumberOfVertices(12); var vertexInput = model.primitives[i].getVertexInput(); var buffRequest = { hash: points.getMTime(), source: points, time: points.getMTime(), usage: BufferUsage.RawVertex, format: 'float32x3' }; if (!device.getBufferManager().hasBuffer(buffRequest)) { // xyz v1 v2 v3 var tmpVBO = new Float32Array(numPoints * 3); var pointIdx = 0; var vboIdx = 0; for (var id = 0; id < numPoints; ++id) { pointIdx = id * 3; tmpVBO[vboIdx++] = pointArray[pointIdx]; tmpVBO[vboIdx++] = pointArray[pointIdx + 1]; tmpVBO[vboIdx++] = pointArray[pointIdx + 2]; } buffRequest.nativeArray = tmpVBO; var buff = device.getBufferManager().getBuffer(buffRequest); vertexInput.addBuffer(buff, ['vertexBC'], 'instance'); } // compute offset VBO var scales = null; if (model.renderable.getScaleArray() != null && pointData.hasArray(model.renderable.getScaleArray())) { scales = pointData.getArray(model.renderable.getScaleArray()).getData(); } var defaultRadius = model.renderable.getRadius(); if (scales || defaultRadius !== model._lastRadius) { buffRequest = { hash: scales, source: scales, time: scales ? pointData.getArray(model.renderable.getScaleArray()).getMTime() : 0, usage: BufferUsage.RawVertex, format: 'float32' }; if (!device.getBufferManager().hasBuffer(buffRequest)) { var _tmpVBO = new Float32Array(numPoints); var _vboIdx = 0; for (var _id = 0; _id < numPoints; ++_id) { var radius = model.renderable.getRadius(); if (scales) { radius = scales[_id * 2 + 1]; } _tmpVBO[_vboIdx++] = radius; } buffRequest.nativeArray = _tmpVBO; var _buff = device.getBufferManager().getBuffer(buffRequest); vertexInput.addBuffer(_buff, ['radiusMC'], 'instance'); } model._lastRadius = defaultRadius; } var orientationArray = null; if (model.renderable.getOrientationArray() != null && pointData.hasArray(model.renderable.getOrientationArray())) { orientationArray = pointData.getArray(model.renderable.getOrientationArray()).getData(); } else { vtkErrorMacro(['Error setting orientationArray.\n', 'You have to specify the stick orientation']); } buffRequest = { hash: scales, source: orientationArray, time: pointData.getArray(model.renderable.getOrientationArray()).getMTime(), usage: BufferUsage.RawVertex, format: 'float32x3' }; if (!device.getBufferManager().hasBuffer(buffRequest)) { // xyz v1 v2 v3 var _tmpVBO2 = new Float32Array(numPoints * 3); var _pointIdx = 0; var _vboIdx2 = 0; for (var _id2 = 0; _id2 < numPoints; ++_id2) { _pointIdx = _id2 * 3; var length = model.renderable.getLength(); if (scales) { length = scales[_id2 * 2]; } _tmpVBO2[_vboIdx2++] = orientationArray[_pointIdx] * length; _tmpVBO2[_vboIdx2++] = orientationArray[_pointIdx + 1] * length; _tmpVBO2[_vboIdx2++] = orientationArray[_pointIdx + 2] * length; } buffRequest.nativeArray = _tmpVBO2; var _buff2 = device.getBufferManager().getBuffer(buffRequest); vertexInput.addBuffer(_buff2, ['orientMC'], 'instance'); } model.renderable.mapScalars(poly, 1.0); // deal with colors but only if modified var haveColors = false; if (model.renderable.getScalarVisibility()) { var c = model.renderable.getColorMapColors(); if (c) { buffRequest = { hash: c, source: c, time: c.getMTime(), usage: BufferUsage.RawVertex, format: 'unorm8x4' }; if (!device.getBufferManager().hasBuffer(buffRequest)) { var colorComponents = c.getNumberOfComponents(); if (colorComponents !== 4) { vtkErrorMacro('this should be 4'); } var _tmpVBO3 = new Uint8Array(numPoints * 4); var _vboIdx3 = 0; var colorData = c.getData(); for (var _id3 = 0; _id3 < numPoints; ++_id3) { var colorIdx = _id3 * colorComponents; _tmpVBO3[_vboIdx3++] = colorData[colorIdx]; _tmpVBO3[_vboIdx3++] = colorData[colorIdx + 1]; _tmpVBO3[_vboIdx3++] = colorData[colorIdx + 2]; _tmpVBO3[_vboIdx3++] = colorData[colorIdx + 3]; } buffRequest.nativeArray = _tmpVBO3; var _buff3 = device.getBufferManager().getBuffer(buffRequest); vertexInput.addBuffer(_buff3, ['colorVI'], 'instance'); } haveColors = true; } } if (!haveColors) { vertexInput.removeBufferIfPresent('colorVI'); } primHelper.setPipelineHash(publicAPI.computePipelineHash(vertexInput)); primHelper.setWebGPURenderer(model.WebGPURenderer); primHelper.setTopology('triangle-list'); primHelper.build(model.renderEncoder, device); primHelper.registerToDraw(); }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- var DEFAULT_VALUES = {}; // ---------------------------------------------------------------------------- function extend(publicAPI, model) { var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; Object.assign(model, DEFAULT_VALUES, initialValues); // Inheritance vtkWebGPUPolyDataMapper.extend(publicAPI, model, initialValues); model.primitives[PrimitiveTypes.Triangles].setVertexShaderTemplate(vtkWebGPUStickMapperVS); // Object methods vtkWebGPUStickMapper(publicAPI, model); var sr = model.primitives[PrimitiveTypes.Triangles].getShaderReplacements(); sr.set('replaceShaderPosition', publicAPI.replaceShaderPosition); sr.set('replaceShaderNormal', publicAPI.replaceShaderNormal); } // ---------------------------------------------------------------------------- var newInstance = newInstance$1(extend, 'vtkWebGPUStickMapper'); // ---------------------------------------------------------------------------- var index = { newInstance: newInstance, extend: extend }; // Register ourself to WebGPU backend if imported registerOverride('vtkStickMapper', newInstance); export default index; export { extend, newInstance };