src/engine/actvolume/floodfill.js (182 lines of code) (raw):

/* * Copyright 2021 EPAM Systems, Inc. (https://www.epam.com/) * SPDX-License-Identifier: Apache-2.0 */ /** * Flood fill 3d volume * @module lib/scripts/actvolume/floodfill */ // absolute imports import * as THREE from 'three'; /** * Class FloodFillTool used to fill areas in 3d * @class FloodFillTool */ export default class FloodFillTool { constructoir() { this.m_stack3d = []; this.m_maxStack3d = 0; this.m_indexStack3d = 0; this.m_numFilled3d = 0; } createStack3d(numPixelsAll) { const MAGIC_STCAK_ESTIMATE = 0.3; this.m_maxStack3d = Math.floor(numPixelsAll * MAGIC_STCAK_ESTIMATE); this.m_stack3d = []; for (let i = 0; i < this.m_maxStack3d; i++) { this.m_stack3d.push(new THREE.Vector3()); } this.m_indexStack3d = 0; this.m_numFilled3d = 0; return 1; } destroyStack3d() { this.m_stack3d = null; } stack3dPush(v) { if (this.m_indexStack3d >= this.m_maxStack3d) { return 0; } this.m_stack3d[this.m_indexStack3d].x = v.x; this.m_stack3d[this.m_indexStack3d].y = v.y; this.m_stack3d[this.m_indexStack3d].z = v.z; this.m_indexStack3d++; return 1; } stack3dPop() { if (this.m_indexStack3d <= 0) { return null; } this.m_indexStack3d--; const v = new THREE.Vector3(); v.x = this.m_stack3d[this.m_indexStack3d].x; v.y = this.m_stack3d[this.m_indexStack3d].y; v.z = this.m_stack3d[this.m_indexStack3d].z; return v; } stack3dEIsEmpty() { return this.m_indexStack3d <= 0; } detectSeedPoint3d(xDim, yDim, zDim, pixels, vaSeedPoints, maxSeedPoints) { const TWO = 2; const yDimHalf = Math.floor(yDim / TWO); const zDimHalf = Math.floor(zDim / TWO); const numItersY = (yDimHalf - 1) * TWO; const numItersZ = (zDimHalf - 1) * TWO; let numSeedPointsDetected = 0; for (let zIter = 0; zIter < numItersZ; zIter++) { let zStep = Math.floor(zIter / TWO); if ((zIter & 1) !== 0) { zStep = -zStep; } const z = zDimHalf + zStep; const zOff = z * xDim * yDim; for (let yIter = 0; yIter < numItersY; yIter++) { let yStep = Math.floor(yIter / TWO); if ((yIter & 1) !== 0) { yStep = -yStep; } const y = yDimHalf + yStep; const yOff = y * xDim; let xL = -1; let xR = -1; let x; for (x = 0; x < xDim - 1; x++) { const isStartInside = pixels[x + yOff + zOff] > 0 && pixels[x + 1 + yOff + zOff] === 0; const isEndInside = pixels[x + yOff + zOff] === 0 && pixels[x + 1 + yOff + zOff] > 0; if (isStartInside) { xL = x; } if (isEndInside && xL >= 0) { xR = x + 1; const xSegmentLen = xR - xL + 1; if (xSegmentLen > 1) { vaSeedPoints[numSeedPointsDetected].x = Math.floor((xL + xR) / TWO); vaSeedPoints[numSeedPointsDetected].y = y; vaSeedPoints[numSeedPointsDetected].z = z; numSeedPointsDetected++; if (numSeedPointsDetected >= maxSeedPoints) { return numSeedPointsDetected; } } } } } // for (yIter) } // for (zIter) return numSeedPointsDetected; } floodFill3d(xDim, yDim, zDim, pixels, vSeed) { const xyDim = xDim * yDim; const okCreate = this.createStack3d(xDim * yDim * zDim); if (okCreate !== 1) { return okCreate; } const VIS = 255; this.m_numFilled3d = 0; this.stack3dPush(vSeed); while (!this.stack3dEIsEmpty()) { let x; const vTaken = this.stack3dPop(); const y = vTaken.y; const z = vTaken.z; const yOff = y * xDim; const zOff = z * xyDim; // get leftmost for (x = vTaken.x; x >= 0 && pixels[x + yOff + zOff] === 0; ) { x--; } const xL = x + 1; // get rightmost for (x = vTaken.x; x < xDim && pixels[x + yOff + zOff] === 0; ) { x++; } const xR = x - 1; let setYLess = VIS; let setYMore = VIS; let setZLess = VIS; let setZMore = VIS; for (x = xL; x <= xR; x++) { // set point visible pixels[x + yOff + zOff] = VIS; this.m_numFilled3d++; // check line y less if (y > 0 && pixels[x + yOff - xDim + zOff] !== setYLess) { setYLess = VIS - setYLess; if (setYLess === 0) { // V3d vPu(x, y - 1, z); const vPu = new THREE.Vector3(x, y - 1, z); const okPush = this.stack3dPush(vPu); if (okPush !== 1) { return okPush; } } // if need to push } // if (y less pint has another vis state // check line above if (y < yDim - 1 && pixels[x + yOff + xDim + zOff] !== setYMore) { setYMore = VIS - setYMore; if (setYMore === 0) { // V3d vPu(x, y + 1, z); const vPu = new THREE.Vector3(x, y + 1, z); const okPush = this.stack3dPush(vPu); if (okPush !== 1) { return okPush; } } // if need to push } // if (y more point has another vis state // check line z less if (z > 0 && pixels[x + yOff + zOff - xyDim] !== setZLess) { setZLess = VIS - setZLess; if (setZLess === 0) { // V3d vPu(x, y, z - 1); const vPu = new THREE.Vector3(x, y, z - 1); const okPush = this.stack3dPush(vPu); if (okPush !== 1) { return okPush; } } // if need to push } // if (z less pint has another vis state // check line z more if (z < zDim - 1 && pixels[x + yOff + zOff + xyDim] !== setZMore) { setZMore = VIS - setZMore; if (setZMore === 0) { // V3d vPu(x, y, z + 1); const vPu = new THREE.Vector3(x, y, z + 1); const okPush = this.stack3dPush(vPu); if (okPush !== 1) { return okPush; } } // if need to push } // if (z more) } // for (x) scanned hor line } // while stack is not empty this.destroyStack3d(); return 1; } } // class