Follower

Introduction

vtkFollower is a subclass of Actor that always faces the camera.

You must set the camera before use. This class will update the matrix such
that the follower always faces the camera. Sepcifically the y axis will up
up, the Z axes will point to the camera and the x axis will point to the
right. You may need to rotate, scale, position the follower to get your data
oriented propoerly for this convention.

If useViewUp is set then instea dof using the camera’s view up the follow’s
viewUp will be used. This is usefull in cases where you want up to be locked
independent of the camera. This is typically the case for VR or AR
annotations where the headset may tilt but text should continue to be
relative to a constant view up vector.

Methods

computeMatrix

Generate the matrix based on ivars.

extend

Method use to decorate a given object (publicAPI+model) with vtkFollower characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues IFollowerInitialValues No (default: {})

getCamera

Get the camera to follow.

getUseViewUp

Check whether the view up vector is used.

getViewUp

Get the view up vector.

getViewUpByReference

Get a reference to the view up vector.

newInstance

Method use to create a new instance of vtkFollower

Argument Type Required Description
initialValues IFollowerInitialValues No for pre-setting some of its content

setCamera

Set the camera to follow.
If this is not set, then the follower won’t know what to follow.

Argument Type Required Description
camera vtkCamera Yes

setUseViewUp

Set whether to use the view up vector.

Argument Type Required Description
useViewUp Boolean Yes

setViewUp

Set the viewUp vector.

Argument Type Required Description
viewUp Array. Yes The view up vector.

Source

index.d.ts
import vtkActor from "vtk.js/Sources/Rendering/Core/Actor";
import vtkCamera from 'vtk.js/Sources/Rendering/Core/Camera';

/**
*
*/
interface IFollowerInitialValues {
viewUp?: number[],
useViewUp?: boolean,
camera?: vtkCamera,
}

/**
*
*/
export interface vtkFollower extends vtkActor {
/**
* Generate the matrix based on ivars.
*/
computeMatrix(): void;

/**
* Get the camera to follow.
*/
getCamera(): vtkCamera;

/**
* Check whether the view up vector is used.
*/
getUseViewUp(): boolean;

/**
* Get the view up vector.
*/
getViewUp(): number[];

/**
* Get a reference to the view up vector.
*/
getViewUpByReference(): number[];

/**
* Set the camera to follow.
* If this is not set, then the follower won't know what to follow.
* @param {vtkCamera} camera
*/
setCamera(camera: vtkCamera): boolean;

/**
* Set whether to use the view up vector.
* @param {Boolean} useViewUp
*/
setUseViewUp(useViewUp: boolean): boolean;

/**
* Set the viewUp vector.
* @param {Number[]} viewUp The view up vector.
*/
setViewUp(viewUp: number[]): boolean;

}

/**
* Method use to decorate a given object (publicAPI+model) with vtkFollower characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {IFollowerInitialValues} [initialValues] (default: {})
*/
export function extend(publicAPI: object, model: object, initialValues?: IFollowerInitialValues): void;

/**
* Method use to create a new instance of vtkFollower
* @param {IFollowerInitialValues} [initialValues] for pre-setting some of its content
*/
export function newInstance(initialValues?: IFollowerInitialValues): vtkFollower;

/**
* vtkFollower is a subclass of Actor that always faces the camera.
*
* You must set the camera before use. This class will update the matrix such
* that the follower always faces the camera. Sepcifically the y axis will up
* up, the Z axes will point to the camera and the x axis will point to the
* right. You may need to rotate, scale, position the follower to get your data
* oriented propoerly for this convention.
*
* If useViewUp is set then instea dof using the camera's view up the follow's
* viewUp will be used. This is usefull in cases where you want up to be locked
* independent of the camera. This is typically the case for VR or AR
* annotations where the headset may tilt but text should continue to be
* relative to a constant view up vector.
*/
export declare const vtkFollower: {
newInstance: typeof newInstance,
extend: typeof extend,
};
export default vtkFollower;
index.js
import { vec3, mat4 } from 'gl-matrix';
import macro from 'vtk.js/Sources/macro';
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';

// ----------------------------------------------------------------------------
// vtkFollower methods
// ----------------------------------------------------------------------------

function vtkFollower(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkFollower');

// Capture 'parentClass' api for internal use
const superClass = { ...publicAPI };

publicAPI.getMTime = () => {
let mt = superClass.getMTime();
if (model.camera !== null) {
const time = model.camera.getMTime();
mt = time > mt ? time : mt;
}

return mt;
};

publicAPI.computeMatrix = () => {
// check whether or not need to rebuild the matrix
if (publicAPI.getMTime() > model.matrixMTime.getMTime()) {
mat4.identity(model.matrix);
if (model.userMatrix) {
mat4.multiply(model.matrix, model.matrix, model.userMatrix);
}
mat4.translate(model.matrix, model.matrix, model.origin);
mat4.translate(model.matrix, model.matrix, model.position);
mat4.multiply(model.matrix, model.matrix, model.rotation);
mat4.scale(model.matrix, model.matrix, model.scale);

if (model.camera) {
// first compute our target viewUp
const vup = new Float64Array(model.viewUp);
if (!model.useViewUp) {
vec3.set(vup, ...model.camera.getViewUp());
}

// compute a vpn
const vpn = new Float64Array(3);
if (model.camera.getParallelProjection()) {
vec3.set(vpn, model.camera.getViewPlaneNormal());
} else {
vec3.set(vpn, ...model.position);
vec3.subtract(vpn, model.camera.getPosition(), vpn);
vec3.normalize(vpn, vpn);
}

// compute vright
const vright = new Float64Array(3);
vec3.cross(vright, vup, vpn);
vec3.normalize(vright, vright);

// now recompute the vpn so that it is orthogonal to vup
vec3.cross(vpn, vright, vup);
vec3.normalize(vpn, vpn);

model.followerMatrix[0] = vright[0];
model.followerMatrix[1] = vright[1];
model.followerMatrix[2] = vright[2];

model.followerMatrix[4] = vup[0];
model.followerMatrix[5] = vup[1];
model.followerMatrix[6] = vup[2];

model.followerMatrix[8] = vpn[0];
model.followerMatrix[9] = vpn[1];
model.followerMatrix[10] = vpn[2];

mat4.multiply(model.matrix, model.followerMatrix, model.matrix);
}

mat4.translate(model.matrix, model.matrix, [
-model.origin[0],
-model.origin[1],
-model.origin[2],
]);
mat4.transpose(model.matrix, model.matrix);

// check for identity
model.isIdentity = false;
model.matrixMTime.modified();
}
};
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
viewUp: [0, 1, 0],
useViewUp: false,
camera: null,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Inheritance
vtkActor.extend(publicAPI, model, initialValues);

model.followerMatrix = mat4.identity(new Float64Array(16));

// Build VTK API
macro.setGet(publicAPI, model, ['useViewUp', 'camera']);

macro.setGetArray(publicAPI, model, ['viewUp'], 3);

// Object methods
vtkFollower(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkFollower');

// ----------------------------------------------------------------------------

export default { newInstance, extend };