@acransac/vtk.js
Version:
Visualization Toolkit for the Web
381 lines (318 loc) • 11.7 kB
JavaScript
import macro from 'vtk.js/Sources/macro';
import { vec3 } from 'gl-matrix';
export default function widgetBehavior(publicAPI, model) {
model.classHierarchy.push('vtkSplineWidgetProp');
// --------------------------------------------------------------------------
// Private methods
// --------------------------------------------------------------------------
const updateHandlesSize = () => {
if (model.handleSizeInPixels !== null) {
const scale =
model.handleSizeInPixels *
vec3.distance(
model.openGLRenderWindow.displayToWorld(0, 0, 0, model.renderer),
model.openGLRenderWindow.displayToWorld(1, 0, 0, model.renderer)
);
model.moveHandle.setScale1(scale);
model.widgetState.getHandleList().forEach((handle) => {
handle.setScale1(scale);
});
}
};
// --------------------------------------------------------------------------
const addPoint = () => {
// Commit handle to location
if (
!model.lastHandle ||
model.keysDown.Control ||
!model.freeHand ||
vec3.squaredDistance(
model.moveHandle.getOrigin(),
model.lastHandle.getOrigin()
) >
model.freehandMinDistance * model.freehandMinDistance
) {
model.lastHandle = model.widgetState.addHandle();
model.lastHandle.setOrigin(...model.moveHandle.getOrigin());
model.lastHandle.setColor(model.moveHandle.getColor());
model.lastHandle.setScale1(model.moveHandle.getScale1());
if (!model.firstHandle) {
model.firstHandle = model.lastHandle;
}
model.openGLRenderWindow.setCursor('grabbing');
}
};
// --------------------------------------------------------------------------
const getHoveredHandle = () => {
const handles = model.widgetState.getHandleList();
return handles.reduce(
({ closestHandle, closestDistance }, handle) => {
const distance = vec3.squaredDistance(
model.moveHandle.getOrigin(),
handle.getOrigin()
);
if (handle !== model.moveHandle) {
return {
closestHandle: distance < closestDistance ? handle : closestHandle,
closestDistance:
distance < closestDistance ? distance : closestDistance,
};
}
return {
closestHandle,
closestDistance,
};
},
{
closestHandle: null,
closestDistance:
model.moveHandle.getScale1() * model.moveHandle.getScale1(),
}
).closestHandle;
};
// --------------------------------------------------------------------------
// Display 2D
// --------------------------------------------------------------------------
publicAPI.setDisplayCallback = (callback) =>
model.representations[0].setDisplayCallback(callback);
// --------------------------------------------------------------------------
// Public methods
// --------------------------------------------------------------------------
macro.setGet(publicAPI, model, [
'freehandMinDistance',
'allowFreehand',
'resolution',
'defaultCursor',
'handleSizeInPixels',
]);
// --------------------------------------------------------------------------
const superSetHandleSizeInPixels = publicAPI.setHandleSizeInPixels;
publicAPI.setHandleSizeInPixels = (size) => {
superSetHandleSizeInPixels(size);
updateHandlesSize();
};
publicAPI.setHandleSizeInPixels(model.handleSizeInPixels); // set initial value
// --------------------------------------------------------------------------
const superSetResolution = publicAPI.setResolution;
publicAPI.setResolution = (resolution) => {
superSetResolution(resolution);
model.representations[1].setResolution(model.resolution);
};
publicAPI.setResolution(model.resolution); // set initial value
// --------------------------------------------------------------------------
publicAPI.getPoints = () =>
model.representations[1].getOutputData().getPoints().getData();
// --------------------------------------------------------------------------
publicAPI.reset = () => {
model.widgetState.clearHandleList();
model.lastHandle = null;
model.firstHandle = null;
};
// --------------------------------------------------------------------------
// Right click: Delete handle
// --------------------------------------------------------------------------
publicAPI.handleRightButtonPress = (e) => {
if (
!model.activeState ||
!model.activeState.getActive() ||
!model.pickable
) {
return macro.VOID;
}
if (model.activeState !== model.moveHandle) {
model.interactor.requestAnimation(publicAPI);
model.activeState.deactivate();
model.widgetState.removeHandle(model.activeState);
model.activeState = null;
model.interactor.cancelAnimation(publicAPI);
} else {
const handle = getHoveredHandle();
if (handle) {
model.widgetState.removeHandle(handle);
} else if (model.lastHandle) {
model.widgetState.removeHandle(model.lastHandle);
const handles = model.widgetState.getHandleList();
model.lastHandle = handles[handles.length - 1];
}
}
publicAPI.invokeInteractionEvent();
return macro.EVENT_ABORT;
};
// --------------------------------------------------------------------------
// Left press: Add new point
// --------------------------------------------------------------------------
publicAPI.handleLeftButtonPress = (e) => {
if (
!model.activeState ||
!model.activeState.getActive() ||
!model.pickable
) {
return macro.VOID;
}
if (model.activeState === model.moveHandle) {
if (model.widgetState.getHandleList().length === 0) {
publicAPI.invokeStartInteractionEvent();
addPoint();
} else {
const hoveredHandle = getHoveredHandle();
if (hoveredHandle && !model.keysDown.Control) {
model.moveHandle.deactivate();
model.moveHandle.setVisible(false);
model.activeState = hoveredHandle;
hoveredHandle.activate();
model.isDragging = true;
model.lastHandle.setVisible(true);
} else {
addPoint();
}
}
model.freeHand = model.allowFreehand && !model.isDragging;
} else {
model.isDragging = true;
model.openGLRenderWindow.setCursor('grabbing');
model.interactor.requestAnimation(publicAPI);
publicAPI.invokeStartInteractionEvent();
}
return macro.EVENT_ABORT;
};
// --------------------------------------------------------------------------
// Left release
// --------------------------------------------------------------------------
publicAPI.handleLeftButtonRelease = (e) => {
if (model.isDragging) {
if (!model.hasFocus) {
model.openGLRenderWindow.setCursor(model.defaultCursor);
model.widgetState.deactivate();
model.interactor.cancelAnimation(publicAPI);
publicAPI.invokeEndInteractionEvent();
} else {
model.moveHandle.setOrigin(...model.activeState.getOrigin());
model.activeState.deactivate();
model.moveHandle.activate();
model.activeState = model.moveHandle;
if (!model.draggedPoint) {
if (
vec3.squaredDistance(
model.moveHandle.getOrigin(),
model.lastHandle.getOrigin()
) <
model.moveHandle.getScale1() * model.moveHandle.getScale1() ||
vec3.squaredDistance(
model.moveHandle.getOrigin(),
model.firstHandle.getOrigin()
) <
model.moveHandle.getScale1() * model.moveHandle.getScale1()
) {
model.lastHandle.setVisible(true);
publicAPI.loseFocus();
}
}
model.interactor.render();
}
} else if (model.activeState !== model.moveHandle) {
model.widgetState.deactivate();
}
model.freeHand = false;
model.isDragging = false;
model.draggedPoint = false;
return model.hasFocus ? macro.EVENT_ABORT : macro.VOID;
};
// --------------------------------------------------------------------------
// Mouse move: Drag selected handle / Handle follow the mouse
// --------------------------------------------------------------------------
publicAPI.handleMouseMove = (callData) => {
if (
!model.activeState ||
!model.activeState.getActive() ||
!model.pickable ||
!model.manipulator
) {
return macro.VOID;
}
model.manipulator.setNormal(model.camera.getDirectionOfProjection());
const worldCoords = model.manipulator.handleEvent(
callData,
model.openGLRenderWindow
);
const hoveredHandle = getHoveredHandle();
if (hoveredHandle) {
model.moveHandle.setVisible(false);
if (hoveredHandle !== model.firstHandle) {
model.openGLRenderWindow.setCursor('grabbing');
}
} else if (!model.isDragging && model.hasFocus) {
model.moveHandle.setVisible(true);
model.openGLRenderWindow.setCursor(model.defaultCursor);
}
if (model.lastHandle) {
model.lastHandle.setVisible(true);
}
if (
worldCoords.length &&
(model.isDragging || model.activeState === model.moveHandle)
) {
model.activeState.setOrigin(worldCoords);
if (model.isDragging) {
model.draggedPoint = true;
}
if (model.freeHand && model.activeState === model.moveHandle) {
addPoint();
}
}
return model.hasFocus ? macro.EVENT_ABORT : macro.VOID;
};
// --------------------------------------------------------------------------
// Mofifier keys
// --------------------------------------------------------------------------
publicAPI.handleKeyDown = ({ key }) => {
model.keysDown[key] = true;
if (!model.hasFocus) {
return;
}
if (key === 'Enter') {
if (model.widgetState.getHandleList().length > 0) {
publicAPI.loseFocus();
}
} else if (key === 'Escape') {
publicAPI.reset();
publicAPI.loseFocus();
} else if (key === 'Delete' || key === 'Backspace') {
if (model.lastHandle) {
model.widgetState.removeHandle(model.lastHandle);
const handleList = model.widgetState.getHandleList();
model.lastHandle = handleList[handleList.length - 1];
}
}
};
// --------------------------------------------------------------------------
publicAPI.handleKeyUp = ({ key }) => {
model.keysDown[key] = false;
};
// --------------------------------------------------------------------------
// Focus API - modeHandle follow mouse when widget has focus
// --------------------------------------------------------------------------
publicAPI.grabFocus = () => {
if (!model.hasFocus) {
model.activeState = model.moveHandle;
model.activeState.activate();
model.activeState.setVisible(true);
model.interactor.requestAnimation(publicAPI);
publicAPI.invokeStartInteractionEvent();
updateHandlesSize();
}
model.hasFocus = true;
};
// --------------------------------------------------------------------------
publicAPI.loseFocus = () => {
if (model.hasFocus) {
model.interactor.cancelAnimation(publicAPI);
publicAPI.invokeEndInteractionEvent();
}
model.widgetState.deactivate();
model.moveHandle.deactivate();
model.moveHandle.setVisible(false);
model.activeState = null;
model.interactor.render();
model.hasFocus = false;
};
}