Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 45 additions & 22 deletions Sources/Filters/Core/Cutter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ function vtkCutter(publicAPI, model) {
const newLinesData = [];
const newPolysData = [];
const newPointData = {}; // TODO: cell data must also be processed
const sign = (x) =>
x - model.cutValue === 0 ? 0 : x < model.cutValue ? -1 : 1;

// Initialize arrays
const numberOfArrays = pointData.getNumberOfArrays();
Expand Down Expand Up @@ -121,16 +123,24 @@ function vtkCutter(publicAPI, model) {
}

// Get associated scalar of points that constitute the current cell
for (let i = 0; i < it.cellSize; ) {
cellPointsScalars[i] = model.cutScalars[it.cell[i++]];
let sideFirstPoint = null;
let firstPointIdx = null;
for (let i = 0; i < it.cellSize; i++) {
cellPointsScalars[i] = model.cutScalars[it.cell[i]];
if (sideFirstPoint === null && cellPointsScalars[i] !== 0) {
sideFirstPoint = sign(cellPointsScalars[i]);
firstPointIdx = i;
}
}

// Check if all cell points are on same side (same side == cell not crossed by cut function)
// TODO: won't work if one point scalar is = 0 ?
const sideFirstPoint = cellPointsScalars[0] > 0;
let allPointsSameSide = true;
for (let i = 1; i < it.cell.length; i++) {
const sideCurrentPoint = cellPointsScalars[i] > 0;
for (let i = 0; i < it.cell.length; i++) {
if (i === firstPointIdx) {
continue;
}
const sideCurrentPoint = sign(cellPointsScalars[i]);
if (sideCurrentPoint !== sideFirstPoint) {
allPointsSameSide = false;
break;
Expand All @@ -143,34 +153,40 @@ function vtkCutter(publicAPI, model) {
}

// Find and compute edges which intersect cells
const intersectedEdgesList = [];
let intersectedEdgesList = [];
for (let i = 0; i < it.cellSize; i++) {
const idNext = i + 1 === it.cellSize ? 0 : i + 1;

// Go to next edge if edge is not crossed
// TODO: in most come cases, (numberOfPointsInCell - 1) or 0 edges of the cell
// will be crossed, but if it crosses right at a point, it could be intersecting
// with (numberOfPoints) or 1 edge(s). Do we account for that?
const signPoint0 = cellPointsScalars[i] > 0;
const signPoint1 = cellPointsScalars[idNext] > 0;

const signPoint0 = sign(cellPointsScalars[i]);
const signPoint1 = sign(cellPointsScalars[idNext]);
if (signPoint1 === signPoint0) {
continue;
}

// Compute preferred interpolation direction
let t = 0.0;
let e1 = i;
let e2 = idNext;
let deltaScalar = cellPointsScalars[e2] - cellPointsScalars[e1];
if (deltaScalar <= 0) {
if (cellPointsScalars[e2] === 0) {
e1 = idNext;
e2 = i;
deltaScalar *= -1;
}
} else if (cellPointsScalars[e1] !== 0) {
let deltaScalar = cellPointsScalars[e2] - cellPointsScalars[e1];
if (deltaScalar <= 0) {
e1 = idNext;
e2 = i;
deltaScalar *= -1;
}

// linear interpolation
let t = 0.0;
if (deltaScalar !== 0.0) {
t = (model.cutValue - cellPointsScalars[e1]) / deltaScalar;
// linear interpolation
if (deltaScalar !== 0.0) {
t = (model.cutValue - cellPointsScalars[e1]) / deltaScalar;
}
}

// points position
Expand Down Expand Up @@ -200,7 +216,7 @@ function vtkCutter(publicAPI, model) {
for (let j = 0; j < n; j++) {
const scalar1 = data[n * pointID1 + j];
const scalar2 = data[n * pointID2 + j];
computedIntersectedArray.push(scalar1 + t * (scalar2 - scalar1)); // FIXME: won't work when the array contains "normals" or "IDs"
computedIntersectedArray[j] = scalar1 + t * (scalar2 - scalar1); // FIXME: won't work when the array contains "normals" or "IDs"
}
computedIntersectedArrays[name] = computedIntersectedArray;
}
Expand All @@ -216,6 +232,8 @@ function vtkCutter(publicAPI, model) {
}

// Add points into newPointList
const ids = new Set();
const dedupedIntersectedEdgesList = [];
for (let i = 0; i < intersectedEdgesList.length; i++) {
const intersectedEdge = intersectedEdgesList[i];
let alreadyAdded = false;
Expand All @@ -234,7 +252,7 @@ function vtkCutter(publicAPI, model) {
crossedEdge.intersectedPoint[2];
if (sameEdge || samePoint) {
alreadyAdded = true;
intersectedEdgesList[i].newPointID = crossedEdges[j].newPointID;
intersectedEdgesList[i].newPointID = crossedEdge.newPointID;
break;
}
}
Expand All @@ -248,19 +266,24 @@ function vtkCutter(publicAPI, model) {
intersectedEdgesList[i].newPointID = newPointsData.length / 3 - 1;
crossedEdges.push(intersectedEdgesList[i]);
}
const newPointID = intersectedEdgesList[i].newPointID;
if (!ids.has(newPointID)) {
dedupedIntersectedEdgesList.push(intersectedEdgesList[i]);
ids.add(newPointID);
}
}

// Store cells
const cellSize = intersectedEdgesList.length;
const cellSize = dedupedIntersectedEdgesList.length;
if (cellSize === 2) {
newLinesData.push(
cellSize,
intersectedEdgesList[0].newPointID,
intersectedEdgesList[1].newPointID
dedupedIntersectedEdgesList[0].newPointID,
dedupedIntersectedEdgesList[1].newPointID
);
} else if (cellSize > 2) {
newPolysData.push(cellSize);
intersectedEdgesList.forEach((edge) => {
dedupedIntersectedEdgesList.forEach((edge) => {
newPolysData.push(edge.newPointID);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ renderer.addActor(dragonActor);
const gui = new GUI();
const state = {
originX: 0,
originY: 0,
originY: 5,
originZ: 0,
normalX: 1,
normalY: 0,
Expand Down Expand Up @@ -174,14 +174,14 @@ originFolder
updatePlaneAndGenerateLoops();
});
originFolder
.add(state, 'originY', -0.5, 0.5, 0.01)
.add(state, 'originY', 0, 10, 0.01)
.name('Y')
.onChange((value) => {
state.originY = Number(value);
updatePlaneAndGenerateLoops();
});
originFolder
.add(state, 'originZ', -0.5, 0.5, 0.01)
.add(state, 'originZ', -3.5, 3.5, 0.01)
.name('Z')
.onChange((value) => {
state.originZ = Number(value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { it, expect } from 'vitest';
import HttpDataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper';
import DataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper';
import vtkCutter from 'vtk.js/Sources/Filters/Core/Cutter';
import vtkContourLoopExtraction from 'vtk.js/Sources/Filters/General/ContourLoopExtraction';
import vtkHttpDataSetReader from 'vtk.js/Sources/IO/Core/HttpDataSetReader';
import vtkPlane from 'vtk.js/Sources/Common/DataModel/Plane';
import 'vtk.js/Sources/IO/Core/DataAccessHelper/JSZipDataAccessHelper';

it('ContourLoopExtraction loops count', async () => {
const plane = vtkPlane.newInstance();
const cutter = vtkCutter.newInstance();

// 1. Fetch the .vtkjs archive as binary.
const zipContent = await HttpDataAccessHelper.fetchBinary(
`${__BASE_PATH__}/data/StanfordDragon.vtkjs`,
{}
);

// 2. Unpack it and get a zip-backed DataAccessHelper once it's ready.
const dataAccessHelper = await new Promise((resolve) => {
const helper = DataAccessHelper.get('zip', {
zipContent,
callback: () => resolve(helper),
});
});

// 3. The root index.json is the *scene* manifest (one entry per actor),
// not a dataset itself — find the item's own index.json inside the zip.
const sceneDescription = await dataAccessHelper.fetchJSON({}, 'index.json');
const item = sceneDescription.scene[0];
const itemUrl = `${item[item.type].url}/index.json`;

// 4. Read that item's index.json as an actual polydata dataset.
const reader = vtkHttpDataSetReader.newInstance({ dataAccessHelper });
await reader.setUrl(itemUrl, { loadData: true });

const polydata = reader.getOutputData();
cutter.setInputData(polydata);
cutter.setCutFunction(plane);

const normals = [
[1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 1.0],
];
const origins = [
[0.0, 0.0, 0.0],
[-100.0, 0.0, 0.0],
[1.2, 0.0, 0.0],
[0.0, 0.0, -0.7],
[0.0, 5.0, 0.0],
];
const expectedNbLoops = [2, 0, 3, 1];

for (let i = 0; i < expectedNbLoops.length; i++) {
plane.setNormal(...normals[i]);
plane.setOrigin(...origins[i]);
const cutterOutput = cutter.getOutputData();
cutterOutput.buildLinks();
const loopExtractor = vtkContourLoopExtraction.newInstance();
loopExtractor.setInputData(cutterOutput);
const outputData = loopExtractor.getOutputData();
expect(outputData.getLines().getNumberOfCells(), 'number of loops').toBe(
expectedNbLoops[i]
);
}
});
Loading