-
-
Notifications
You must be signed in to change notification settings - Fork 382
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Rendering): Add follower class that always faces the camera
Subclass of Actor that always faces the camera.
- Loading branch information
Ken Martin
committed
Jun 22, 2020
1 parent
bb7eab2
commit e8d8325
Showing
5 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
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 | ||
vieUp 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. | ||
|
||
## See Also | ||
|
||
[vtkActor](./Rendering_Core_Actor.html) | ||
|
||
## setCamera() | ||
|
||
Set the camera that this follower should face | ||
|
||
## getMTime() | ||
|
||
Get the newest "modification time" of the actor, its properties, and texture (if set). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
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 = vec3.fromValues(model.viewUp); | ||
if (!model.useViewUp) { | ||
vec3.set(vup, ...model.camera.getViewUp()); | ||
} | ||
|
||
// compute a vpn | ||
const vpn = vec3.create(); | ||
if (model.camera.getParallelProjection()) { | ||
vec3.set(vpn, model.camera.getViewPlaneNormal()); | ||
} else { | ||
vec3.set(vpn, ...model.position); | ||
const tmpv3 = vec3.fromValues(...model.camera.getPosition()); | ||
vec3.subtract(vpn, tmpv3, vpn); | ||
vec3.normalize(vpn, vpn); | ||
} | ||
|
||
// compute vright | ||
const vright = vec3.create(); | ||
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.create(); | ||
mat4.identity(model.followerMatrix); | ||
|
||
// 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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import test from 'tape-catch'; | ||
import testUtils from 'vtk.js/Sources/Testing/testUtils'; | ||
|
||
import vtkFollower from 'vtk.js/Sources/Rendering/Core/Follower'; | ||
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper'; | ||
import vtkOBJReader from 'vtk.js/Sources/IO/Misc/OBJReader'; | ||
import vtkOpenGLRenderWindow from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow'; | ||
import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow'; | ||
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer'; | ||
|
||
import baseline from './testFollower.png'; | ||
|
||
test.onlyIfWebGL('Test Follower class', (t) => { | ||
const gc = testUtils.createGarbageCollector(t); | ||
t.ok('rendering', 'vtkFollower'); | ||
|
||
// Create some control UI | ||
const container = document.querySelector('body'); | ||
const renderWindowContainer = gc.registerDOMElement( | ||
document.createElement('div') | ||
); | ||
container.appendChild(renderWindowContainer); | ||
|
||
// create what we will view | ||
const renderWindow = gc.registerResource(vtkRenderWindow.newInstance()); | ||
const renderer = gc.registerResource(vtkRenderer.newInstance()); | ||
renderWindow.addRenderer(renderer); | ||
renderer.setBackground(0.1, 0.2, 0.4); | ||
|
||
// ---------------------------------------------------------------------------- | ||
// Test code | ||
// ---------------------------------------------------------------------------- | ||
const reader = gc.registerResource( | ||
vtkOBJReader.newInstance({ splitMode: 'usemtl' }) | ||
); | ||
|
||
const mapper = gc.registerResource(vtkMapper.newInstance()); | ||
mapper.setInputConnection(reader.getOutputPort()); | ||
|
||
const actor = gc.registerResource(vtkFollower.newInstance()); | ||
actor.setMapper(mapper); | ||
actor.setCamera(renderer.getActiveCamera()); | ||
renderer.addActor(actor); | ||
|
||
reader | ||
.setUrl( | ||
`${__BASE_PATH__}/Data/obj/space-shuttle-orbiter/space-shuttle-orbiter.obj` | ||
) | ||
.then(() => { | ||
renderer.resetCamera(); | ||
renderWindow.render(); | ||
|
||
const glwindow = gc.registerResource(vtkOpenGLRenderWindow.newInstance()); | ||
glwindow.setContainer(renderWindowContainer); | ||
renderWindow.addView(glwindow); | ||
glwindow.setSize(400, 400); | ||
|
||
renderer.getActiveCamera().azimuth(10); | ||
renderer.getActiveCamera().elevation(10); | ||
renderer.getActiveCamera().orthogonalizeViewUp(); | ||
glwindow.captureNextImage().then((image) => { | ||
testUtils.compareImages( | ||
image, | ||
[baseline], | ||
'Rendering/Core/Follower/testFollower', | ||
t, | ||
1.5, | ||
gc.releaseResources | ||
); | ||
}); | ||
renderWindow.render(); | ||
}); | ||
}); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters