-
Notifications
You must be signed in to change notification settings - Fork 2
Volume agent rendering #419
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
base: volume-agents
Are you sure you want to change the base?
Changes from 15 commits
2dab001
5e14066
84ac658
416e92f
432be1c
3711c9e
1c0d2c6
18118c9
5528961
3923a42
b7dce62
1a9d435
1bbcfd9
d2b8e72
0033592
b9f30b7
c497d69
547f8c0
f21b65b
7fa5e28
d8515ca
9b507cf
f465c10
018d89c
ff87ac3
8b2edd2
d3f590b
7148198
9aeedfd
f42e53c
55699e5
b4df812
a69bb87
6cbd7a1
cbea575
bff5385
7ab44d1
5de80fe
0f12260
b0af148
d6edd91
086fe94
0561f62
78c51cc
2a0159a
b064cb1
121596c
80d0546
baec486
746b4b7
b59b2fb
4527126
6dc215d
9b9e1cd
3e51de0
ac0f5fe
a3aea7e
50a0a30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,136 @@ | ||
| import { Box3Helper, Euler, Object3D, Vector3 } from "three"; | ||
| import { Euler, Object3D, Vector3 } from "three"; | ||
|
|
||
| import { Volume, VolumeDrawable } from "@aics/volume-viewer"; | ||
| import { | ||
| HasThreeJsContext, | ||
| Lut, | ||
| Volume, | ||
| VolumeDrawable, | ||
| } from "@aics/volume-viewer"; | ||
|
|
||
| import { AgentData } from "../simularium/types"; | ||
|
|
||
| // TEMPORARY HACK to make things typecheck. TODO remove! | ||
| import { VolumeRenderImpl } from "@aics/volume-viewer/es/types/VolumeRenderImpl"; | ||
| interface TempRayMarchedVolume extends VolumeRenderImpl { | ||
| boxHelper: Box3Helper; | ||
| } | ||
|
|
||
|
Comment on lines
-7
to
-12
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎉 |
||
| export default class VolumeModel { | ||
| // TODO what to do with this `cancelled` property? Type check fails without it. | ||
| // When should it be set, if ever; what should it be used for, if anything? | ||
| public cancelled = false; | ||
| private image?: VolumeDrawable; | ||
| private drawable?: VolumeDrawable; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I recall, I think the "cancelled" property mentioned above is for when the trajectory has been changed but there is still geometry download requests in-flight. We can use |
||
| private volume?: Volume; | ||
| private channelsEnabled: boolean[] = []; | ||
|
|
||
| private setEnabledChannels(channels: number[]): void { | ||
| if (!this.volume || !this.drawable) { | ||
| this.channelsEnabled = []; | ||
| return; | ||
| } | ||
| const { numChannels } = this.volume.imageInfo; | ||
| this.channelsEnabled = new Array(numChannels).fill(false); | ||
|
|
||
| for (const channel of channels) { | ||
| if (channel < numChannels) { | ||
| this.channelsEnabled[channel] = true; | ||
| } | ||
| } | ||
|
|
||
| for (const [channelIndex, enabled] of this.channelsEnabled.entries()) { | ||
| this.drawable.setVolumeChannelEnabled(channelIndex, enabled); | ||
| } | ||
| } | ||
|
|
||
| public setImage(volumeObject: Volume): void { | ||
| this.image = new VolumeDrawable(volumeObject, {}); | ||
| this.volume = volumeObject; | ||
| this.drawable = new VolumeDrawable(this.volume, {}); | ||
| this.volume.addVolumeDataObserver(this); | ||
| this.drawable.setBrightness(0.7); | ||
| this.drawable.setGamma(0.15, 0.9, 1.0); | ||
| this.drawable.setDensity(0.7); | ||
| } | ||
|
|
||
| public setAgentData(data: AgentData): void { | ||
| if (this.image) { | ||
| this.image.setTranslation(new Vector3(data.x, data.y, data.z)); | ||
| this.image.setRotation(new Euler(data.xrot, data.yrot, data.zrot)); | ||
| if (this.drawable) { | ||
| this.drawable.setTranslation(new Vector3(data.x, data.y, data.z)); | ||
| this.drawable.setRotation( | ||
| new Euler(data.xrot, data.yrot, data.zrot) | ||
| ); | ||
| const r = data.cr * 2; | ||
| this.image.setScale(new Vector3(r, r, r)); | ||
| this.drawable.setScale(new Vector3(r, r, r)); | ||
| // Always defined if `drawable` is, but ts doesn't know that. | ||
| if (this.volume) { | ||
| // Volume agent data may use subpoint 0 as time | ||
| const numPoints = data.subpoints.length; | ||
| const time = numPoints > 0 ? data.subpoints[0] : 0; | ||
| if (this.volume.loadSpec.time !== time) { | ||
| this.volume.updateRequiredData({ time }); | ||
| } | ||
| // If there are more subpoints, they are enabled channel idxes. | ||
| // Otherwise, just channel 0 is enabled. | ||
| const channels = numPoints > 1 ? data.subpoints.slice(1) : [0]; | ||
| this.setEnabledChannels(channels); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public loadInitialData(): void { | ||
| this.volume?.loader?.loadVolumeData(this.volume); | ||
| } | ||
|
|
||
| public getObject3D(): Object3D | undefined { | ||
| return this.image?.sceneRoot; | ||
| return this.drawable?.sceneRoot; | ||
| } | ||
|
|
||
| public onChannelLoaded(vol: Volume, channelIndex: number): void { | ||
| if (this.drawable) { | ||
| const isEnabled = this.channelsEnabled[channelIndex]; | ||
| this.drawable.setVolumeChannelEnabled(channelIndex, isEnabled); | ||
| this.drawable.updateScale(); | ||
| this.drawable.onChannelLoaded([channelIndex]); | ||
| if (this.volume) { | ||
| const histo = this.volume.getHistogram(channelIndex); | ||
| const min = histo.findBinOfPercentile(0.5); | ||
| const max = histo.findBinOfPercentile(0.983); | ||
|
Comment on lines
+94
to
+95
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I saw the note you had in the PR description, maybe tag all the hardcoded values with a TODO so they're easier to track down? Totally optional |
||
| const lut = new Lut().createFromMinMax(min, max); | ||
| this.volume.setLut(channelIndex, lut); | ||
| } | ||
| this.drawable.updateLuts(); | ||
| this.drawable.fuse(); | ||
| } | ||
| } | ||
|
|
||
| public onBeforeRender( | ||
| context: HasThreeJsContext, | ||
| width: number, | ||
| height: number, | ||
| orthoScale: number | undefined | ||
| ): void { | ||
| if (this.drawable) { | ||
| const isOrtho = orthoScale !== undefined; | ||
| this.drawable.setIsOrtho(isOrtho); | ||
| if (isOrtho) { | ||
| this.drawable.setOrthoScale(orthoScale); | ||
| } | ||
| this.drawable.setResolution(width, height); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does setting the resolution here do? |
||
| this.drawable.onAnimate(context); | ||
| } | ||
| } | ||
|
|
||
| public setSize(width: number, height: number): void { | ||
| this.drawable?.setResolution(width, height); | ||
| } | ||
|
|
||
| // METHODS FROM `VolumeDataObserver` in volume-viewer | ||
|
|
||
| public onVolumeData(_volume: Volume, channels: number[]): void { | ||
| this.drawable?.updateScale(); | ||
| this.drawable?.onChannelLoaded(channels); | ||
| } | ||
|
|
||
| public onVolumeChannelAdded( | ||
| _volume: Volume, | ||
| newChannelIndex: number | ||
| ): void { | ||
| this.drawable?.onChannelAdded(newChannelIndex); | ||
| } | ||
|
|
||
| public tempGetBoundingBoxObject(): Box3Helper | undefined { | ||
| this.image?.setShowBoundingBox(true); | ||
| return (this.image?.volumeRendering as TempRayMarchedVolume)?.boxHelper; | ||
| public onVolumeLoadError(_volume: Volume, error: unknown): void { | ||
| console.error("Volume load error", error); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,7 @@ import { | |
| } from "./types"; | ||
| import { checkAndSanitizePath } from "../util"; | ||
| import ColorHandler from "./ColorHandler"; | ||
| import { HasThreeJsContext } from "@aics/volume-viewer"; | ||
|
|
||
| const MAX_PATH_LEN = 32; | ||
| const MAX_MESHES = 100000; | ||
|
|
@@ -152,7 +153,7 @@ class VisGeometry { | |
| public lightsGroup: Group; | ||
| public agentPathGroup: Group; | ||
| public instancedMeshGroup: Group; | ||
| public tempVolumeGroup: Group; // TODO remove | ||
| public volumeGroup: Group; // TODO remove | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the TODO still needed? |
||
| private supportsWebGL2Rendering: boolean; | ||
| private lodBias: number; | ||
| private lodDistanceStops: number[]; | ||
|
|
@@ -219,9 +220,9 @@ class VisGeometry { | |
| this.instancedMeshGroup = new Group(); | ||
| this.instancedMeshGroup.name = "instanced meshes for agents"; | ||
| this.scene.add(this.instancedMeshGroup); | ||
| this.tempVolumeGroup = new Group(); | ||
| this.tempVolumeGroup.name = "volumes"; | ||
| this.scene.add(this.tempVolumeGroup); | ||
| this.volumeGroup = new Group(); | ||
| this.volumeGroup.name = "volumes"; | ||
| this.scene.add(this.volumeGroup); | ||
|
|
||
| this.resetBounds(DEFAULT_VOLUME_DIMENSIONS); | ||
|
|
||
|
|
@@ -995,10 +996,20 @@ class VisGeometry { | |
| for (let i = this.instancedMeshGroup.children.length - 1; i >= 0; i--) { | ||
| this.instancedMeshGroup.remove(this.instancedMeshGroup.children[i]); | ||
| } | ||
| for (let i = this.tempVolumeGroup.children.length - 1; i >= 0; i--) { | ||
| this.tempVolumeGroup.remove(this.tempVolumeGroup.children[i]); | ||
| for (let i = this.volumeGroup.children.length - 1; i >= 0; i--) { | ||
| this.volumeGroup.remove(this.volumeGroup.children[i]); | ||
| } | ||
|
|
||
| const volRenderContext: HasThreeJsContext = { | ||
| camera: this.camera, | ||
| // if not for `renderer` already being used for | ||
| // `SimulariumRenderer`, we could use `this` below. | ||
| renderer: this.threejsrenderer, | ||
| }; | ||
|
|
||
| const canvasWidth = this.threejsrenderer.domElement.width; | ||
| const canvasHeight = this.threejsrenderer.domElement.height; | ||
|
|
||
| // re-add fibers immediately | ||
| this.instancedMeshGroup.add(this.fibers.getGroup()); | ||
|
|
||
|
|
@@ -1025,9 +1036,20 @@ class VisGeometry { | |
| } | ||
| } | ||
| } else if (displayType === GeometryDisplayType.VOLUME) { | ||
| const volObject = entry.geometry.tempGetBoundingBoxObject(); | ||
| if (volObject) { | ||
| this.tempVolumeGroup.add(volObject); | ||
| const volObj = entry.geometry.getObject3D(); | ||
| if (volObj) { | ||
| const isOrtho = (this.camera as OrthographicCamera) | ||
| .isOrthographicCamera; | ||
| const orthoScale = isOrtho | ||
| ? 1 / this.camera.zoom | ||
| : undefined; | ||
| entry.geometry.onBeforeRender( | ||
| volRenderContext, | ||
| canvasWidth, | ||
| canvasHeight, | ||
| orthoScale | ||
| ); | ||
| this.volumeGroup.add(volObj); | ||
| } | ||
| } else { | ||
| const meshEntry = entry as MeshGeometry; | ||
|
|
@@ -1060,7 +1082,7 @@ class VisGeometry { | |
| this.boundingBoxMesh.visible = false; | ||
| this.tickMarksMesh.visible = false; | ||
| this.agentPathGroup.visible = false; | ||
| this.tempVolumeGroup.visible = false; | ||
| this.volumeGroup.visible = false; | ||
| this.renderer.render( | ||
| this.threejsrenderer, | ||
| this.scene, | ||
|
|
@@ -1072,7 +1094,7 @@ class VisGeometry { | |
| this.boundingBoxMesh.visible = true; | ||
| this.tickMarksMesh.visible = true; | ||
| this.agentPathGroup.visible = true; | ||
| this.tempVolumeGroup.visible = true; | ||
| this.volumeGroup.visible = true; | ||
|
|
||
| this.threejsrenderer.autoClear = false; | ||
| // hide everything except the wireframe and paths, and render with the standard renderer | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to bind the callback here? :0