Skip to content

Actor2d/overlay layering support #3209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 3, 2025
22 changes: 22 additions & 0 deletions Sources/Rendering/Core/Actor2D/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,28 @@ export interface vtkActor2D extends vtkProp {
*/
getMapper(): vtkMapper2D;

/**
* Set the layer number for this 2D actor.
* The scenegraph uses this layer number to sort actor 2D overlays/underlays on top of each other.
* The actor2D with the highest layer number is going to be rendered at the very front i.e. it is
* the top-most layer.
* If two actor2D instances share the same layer number, they are rendered in the order in which
* they were added to the renderer via `addActor` or `addActor2D`.
* By default, each actor2D has a layer number of 0.
*/
setLayerNumber(layer: number): void;

/**
* Get the layer number for this 2D actor.
* The scenegraph uses this layer number to sort actor 2D overlays/underlays on top of each other.
* The actor2D with the highest layer number is going to be rendered at the very front i.e. it is
* the top-most layer.
* If two actor2D instances share the same layer number, they are rendered in the order in which
* they were added to the renderer via `addActor` or `addActor2D`.
* By default, each actor2D has a layer number of 0.
*/
getLayerNumber(): number;

/**
*
*/
Expand Down
2 changes: 1 addition & 1 deletion Sources/Rendering/Core/Actor2D/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export function extend(publicAPI, model, initialValues = {}) {

// Build VTK API
macro.set(publicAPI, model, ['property']);
macro.setGet(publicAPI, model, ['mapper']);
macro.setGet(publicAPI, model, ['mapper', 'layerNumber']);

// Object methods
vtkActor2D(publicAPI, model);
Expand Down
25 changes: 22 additions & 3 deletions Sources/Rendering/Core/Actor2D/test/testActor2D.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,27 @@ test.onlyIfWebGL('Test Actor2D', (t) => {
actor2D.getProperty().setOpacity(0.3);
actor2D.getProperty().setDisplayLocation(DisplayLocation.FOREGROUND);
actor2D.getProperty().setRepresentation(Representation.SURFACE);
actor2D.setLayerNumber(2);
renderer.addActor2D(actor2D);
const actor2D1 = gc.registerResource(vtkActor2D.newInstance());
actor2D1.getProperty().setColor([0.1, 0.8, 0.5]);
actor2D1.getProperty().setDisplayLocation(DisplayLocation.BACKGROUND);
actor2D1.getProperty().setRepresentation(Representation.SURFACE);
renderer.addActor2D(actor2D1);
actor2D1.setLayerNumber(1);
const actor2D2 = gc.registerResource(vtkActor2D.newInstance());
actor2D2.getProperty().setColor([0.8, 0.4, 0.4]);
actor2D2.getProperty().setOpacity(1.0);
actor2D2.getProperty().setDisplayLocation(DisplayLocation.FOREGROUND);
actor2D2.getProperty().setRepresentation(Representation.SURFACE);
actor2D2.setLayerNumber(1);
renderer.addActor2D(actor2D2);

const mapper = gc.registerResource(vtkMapper.newInstance());
actor.setMapper(mapper);
const mapper2D = gc.registerResource(vtkMapper2D.newInstance());
const mapper2D1 = gc.registerResource(vtkMapper2D.newInstance());
const mapper2D2 = gc.registerResource(vtkMapper2D.newInstance());
const c = vtkCoordinate.newInstance();
c.setCoordinateSystemToWorld();
mapper2D.setTransformCoordinate(c);
Expand All @@ -62,21 +72,30 @@ test.onlyIfWebGL('Test Actor2D', (t) => {
mapper2D1.setTransformCoordinate(c);
mapper2D1.setScalarVisibility(false);
actor2D1.setMapper(mapper2D1);
mapper2D2.setTransformCoordinate(c);
mapper2D2.setScalarVisibility(false);
actor2D2.setMapper(mapper2D2);

const cubeSource = gc.registerResource(vtkCubeSource.newInstance());
mapper.setInputConnection(cubeSource.getOutputPort());
const sphereSource = gc.registerResource(vtkSphereSource.newInstance());
sphereSource.setCenter(-0.5, 0.0, 0.0);
sphereSource.setRadius(0.3);
sphereSource.setRadius(0.35);
sphereSource.setThetaResolution(25);
sphereSource.setPhiResolution(25);
mapper2D.setInputConnection(sphereSource.getOutputPort());
const sphereSource1 = gc.registerResource(vtkSphereSource.newInstance());
sphereSource1.setCenter(0.5, -0.3, 0.0);
sphereSource1.setRadius(0.3);
sphereSource1.setCenter(0, -0.5, 0.0);
sphereSource1.setRadius(0.45);
sphereSource1.setThetaResolution(30);
sphereSource1.setPhiResolution(30);
mapper2D1.setInputConnection(sphereSource1.getOutputPort());
const sphereSource2 = gc.registerResource(vtkSphereSource.newInstance());
sphereSource2.setCenter(-0.2, -0.3, 0.0);
sphereSource2.setRadius(0.35);
sphereSource2.setThetaResolution(30);
sphereSource2.setPhiResolution(30);
mapper2D2.setInputConnection(sphereSource2.getOutputPort());

renderer.getActiveCamera().azimuth(25);
renderer.getActiveCamera().roll(25);
Expand Down
Binary file modified Sources/Rendering/Core/Actor2D/test/testActor2D.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 13 additions & 3 deletions Sources/Rendering/Core/Viewport/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,20 @@ function vtkViewport(publicAPI, model) {
}

publicAPI.getViewPropsWithNestedProps = () => {
const allPropsArray = [];
for (let i = 0; i < model.props.length; i++) {
gatherProps(model.props[i], allPropsArray);
let allPropsArray = [];
// Handle actor2D instances separately so that they can be overlayed and layered
const actors2D = publicAPI.getActors2D();
// Sort the actor2D list using its layer number
actors2D.sort((a, b) => a.getLayerNumber() - b.getLayerNumber());
// Filter out all the actor2D instances
const newPropList = model.props.filter((item) => !actors2D.includes(item));
for (let i = 0; i < newPropList.length; i++) {
gatherProps(newPropList[i], allPropsArray);
}
// Finally, add the actor2D props at the end of the list
// This works because, when traversing the render pass in vtkOpenGLRenderer, the children are
// traversed in the order that they are added to the list
allPropsArray = allPropsArray.concat(actors2D);
return allPropsArray;
};

Expand Down
2 changes: 1 addition & 1 deletion Sources/Rendering/OpenGL/ForwardPass/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ function vtkForwardPass(publicAPI, model) {
model.translucentActorCount = 0;
model.volumeCount = 0;
model.overlayActorCount = 0;
publicAPI.setCurrentOperation('queryPass');

publicAPI.setCurrentOperation('queryPass');
renNode.traverse(publicAPI);

// do we need to capture a zbuffer?
Expand Down
5 changes: 4 additions & 1 deletion Sources/Rendering/OpenGL/Renderer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ function vtkOpenGLRenderer(publicAPI, model) {
publicAPI.updateLights();
publicAPI.prepareNodes();
publicAPI.addMissingNode(model.renderable.getActiveCamera());
publicAPI.addMissingNodes(model.renderable.getViewPropsWithNestedProps());
publicAPI.addMissingNodes(
model.renderable.getViewPropsWithNestedProps(),
true
);
publicAPI.removeUnusedNodes();
}
};
Expand Down
17 changes: 15 additions & 2 deletions Sources/Rendering/SceneGraph/ViewNode/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,27 @@ function vtkViewNode(publicAPI, model) {

// add missing nodes/children for the passed in renderables. This should
// be called only in between prepareNodes and removeUnusedNodes
publicAPI.addMissingNodes = (dataObjs) => {
publicAPI.addMissingNodes = (dataObjs, enforceOrder = false) => {
if (!dataObjs || !dataObjs.length) {
return;
}

for (let index = 0; index < dataObjs.length; ++index) {
const dobj = dataObjs[index];
publicAPI.addMissingNode(dobj);
const node = publicAPI.addMissingNode(dobj);
if (
enforceOrder &&
node !== undefined &&
model.children[index] !== node
) {
for (let i = index + 1; i < model.children.length; ++i) {
if (model.children[i] === node) {
model.children.splice(i, 1);
model.children.splice(index, 0, node);
break;
}
}
}
}
};

Expand Down