import { AmbientLight , Vector3 , Object3D , SpotLight , DirectionalLight , Euler , Scene } from "three" ; import { ThreeJsPanel } from "./ThreeJsPanel" ; import lightSettings from "./constants/lights" ; import FusedChannelData from "./FusedChannelData" ; import VolumeDrawable from "./VolumeDrawable" ; import { Light , AREA_LIGHT , SKY_LIGHT } from "./Light" ; import Volume from "./Volume" ; import { VolumeChannelDisplayOptions , VolumeDisplayOptions , isOrthographicCamera } from "./types" ; export const RENDERMODE_RAYMARCH = 0 ; export const RENDERMODE_PATHTRACE = 1 ; export interface View3dOptions { useWebGL2 : boolean ; } class export class View3d { private canvas3d : ThreeJsPanel ; private scene : Scene ; private backgroundColor : number ; private pixelSamplingRate : number ; private exposure : number ; private volumeRenderMode : number ; private renderUpdateListener ? : ( iteration : number ) => void ; private loaded : boolean ; private parentEl : HTMLElement ; private image ? : VolumeDrawable ; private oldScale : Vector3 ; private currentScale : Vector3 ; private lights : Light [ ] ; private lightContainer : Object3D ; private ambientLight : AmbientLight ; private spotLight : SpotLight ; private reflectedLight : DirectionalLight ; private fillLight : DirectionalLight ; param { HTMLElement } parentElement param { Object } options constructor ( parentElement , options = { useWebGL2 : true } ) { if ( options . useWebGL2 === undefined ) { options . useWebGL2 = true ; } this . canvas3d = new ThreeJsPanel ( parentElement , options . useWebGL2 ) ; this . redraw = this . redraw . bind ( this ) ; this . scene = new Scene ( ) ; this . backgroundColor = 0x000000 ; this . lights = [ ] ; this . pixelSamplingRate = 0.75 ; this . exposure = 0.5 ; this . volumeRenderMode = RENDERMODE_RAYMARCH ; this . loaded = false ; this . parentEl = parentElement ; window . addEventListener ( "resize" , ( ) => this . resize ( null , this . parentEl . offsetWidth , this . parentEl . offsetHeight ) ) ; this . oldScale = new Vector3 ( ) ; this . currentScale = new Vector3 ( ) ; this . lightContainer = new Object3D ( ) ; this . ambientLight = new AmbientLight ( ) ; this . spotLight = new SpotLight ( ) ; this . reflectedLight = new DirectionalLight ( ) ; this . fillLight = new DirectionalLight ( ) ; this . buildScene ( ) ; FusedChannelData . setOnFuseComplete ( this . redraw . bind ( this ) ) ; } preRender ( ) { const lightContainer = this . scene . getObjectByName ( "lightContainer" ) ; if ( lightContainer ) { lightContainer . rotation . setFromRotationMatrix ( this . canvas3d . camera . matrixWorld ) ; } if ( this . image && isOrthographicCamera ( this . canvas3d . camera ) ) { this . image . setOrthoScale ( this . canvas3d . controls . scale ) ; } } param { Object } dataurlcallback capture ( dataurlcallback ) { return this . canvas3d . requestCapture ( dataurlcallback ) ; } /**
     * Force a redraw.
     */ redraw ( ) { this . canvas3d . redraw ( ) ; } unsetImage ( ) { if ( this . image ) { this . canvas3d . removeControlHandlers ( ) ; this . canvas3d . animateFuncs = [ ] ; this . scene . remove ( this . image . sceneRoot ) ; } return this . image ; } param { Volume } volume param { VolumeDisplayOptions } options addVolume ( volume , options ? ) { volume . addVolumeDataObserver ( this ) ; options = options || { } ; options . renderMode = this . volumeRenderMode === RENDERMODE_PATHTRACE ? 1 : 0 ; this . setImage ( new VolumeDrawable ( volume , options ) ) ; } param { Volume } volume param { number } channelIndex param { VolumeChannelDisplayOptions } options setVolumeChannelOptions ( volume , channelIndex , options ) { if ( ! this . image ) { return ; } this . image . setChannelOptions ( channelIndex , options ) ; this . redraw ( ) ; } param { Volume } volume param { VolumeDisplayOptions } options setVolumeDisplayOptions ( volume , options ) { if ( ! this . image ) { return ; } this . image . setOptions ( options ) ; this . redraw ( ) ; } param { Volume } volume removeVolume ( volume ) { const oldImage = this . unsetImage ( ) ; if ( oldImage ) { oldImage . cleanup ( ) ; } if ( volume ) { volume . removeVolumeDataObserver ( this ) ; } } /**
     * Remove all volume images from the viewer.
     */ removeAllVolumes ( ) { if ( this . image ) { this . removeVolume ( this . image . volume ) ; } } param { function } callback setRenderUpdateListener ( callback ) { this . renderUpdateListener = callback ; if ( this . image ) { this . image . setRenderUpdateListener ( callback ) ; } } onVolumeData ( volume , channels ) { if ( this . image ) { this . image . onChannelLoaded ( channels ) ; } } onVolumeChannelAdded ( volume , newChannelIndex ) { if ( this . image ) { this . image . onChannelAdded ( newChannelIndex ) ; } } param { Object } volume param { number } maskChannelIndex setVolumeChannelAsMask ( volume , maskChannelIndex ) { if ( this . image ) { this . image . setChannelAsMask ( maskChannelIndex ) ; } this . redraw ( ) ; } param { Object } volume param { number } values setVoxelSize ( volume , values ) { if ( this . image ) { this . image . setVoxelSize ( values ) ; } this . redraw ( ) ; } setRayStepSizes ( volume , primary , secondary ) { if ( this . image ) { this . image . setRayStepSizes ( primary , secondary ) ; } this . redraw ( ) ; } setShowBoundingBox ( volume , showBoundingBox ) { if ( this . image ) { this . image . setShowBoundingBox ( showBoundingBox ) ; } this . redraw ( ) ; } setBoundingBoxColor ( volume , color ) { if ( this . image ) { this . image . setBoundingBoxColor ( color ) ; } this . redraw ( ) ; } param { Object } volume param { number } channel param { number } isovalue param { number = } alpha createIsosurface ( volume , channel , isovalue , alpha ) { if ( ! this . image ) { return ; } if ( this . image . hasIsosurface ( channel ) ) { this . image . updateIsovalue ( channel , isovalue ) ; } else { this . image . createIsosurface ( channel , isovalue , alpha , alpha < 0.95 ) ; } this . redraw ( ) ; } param { Object } volume param { number } channel return hasIsosurface ( volume , channel ) { if ( this . image ) { return this . image . hasIsosurface ( channel ) ; } else { return false ; } } param { Object } volume param { number } channel param { number } isovalue updateIsosurface ( volume , channel , isovalue ) { if ( ! this . image || ! this . image . hasIsosurface ( channel ) ) { return ; } this . image . updateIsovalue ( channel , isovalue ) ; this . redraw ( ) ; } param { Object } volume param { number } channel param { number } opacity updateOpacity ( volume , channel , opacity ) { if ( ! this . image ) { return ; } this . image . updateOpacity ( channel , opacity ) ; this . redraw ( ) ; } param { Object } volume param { number } channel clearIsosurface ( volume , channel ) { if ( this . image ) { this . image . destroyIsosurface ( channel ) ; } this . redraw ( ) ; } param { Object } volume param { number } channelIndex param { string } type saveChannelIsosurface ( volume , channelIndex , type ) { if ( this . image ) { this . image . saveChannelIsosurface ( channelIndex , type ) ; } } setImage ( img ) { const oldImage = this . unsetImage ( ) ; this . image = img ; this . scene . add ( img . sceneRoot ) ; this . image . setResolution ( this . canvas3d ) ; this . image . setIsOrtho ( isOrthographicCamera ( this . canvas3d . camera ) ) ; this . image . setBrightness ( this . exposure ) ; this . canvas3d . setControlHandlers ( this . onStartControls . bind ( this ) , this . onChangeControls . bind ( this ) , this . onEndControls . bind ( this ) ) ; this . canvas3d . animateFuncs . push ( this . preRender . bind ( this ) ) ; this . canvas3d . animateFuncs . push ( img . onAnimate . bind ( img ) ) ; this . redraw ( ) ; return oldImage ; } onStartControls ( ) { if ( this . volumeRenderMode !== RENDERMODE_PATHTRACE ) { this . canvas3d . startRenderLoop ( ) ; } if ( this . image ) { this . image . onStartControls ( ) ; } } onChangeControls ( ) { if ( this . image ) { this . image . onChangeControls ( ) ; } } onEndControls ( ) { if ( this . image ) { this . image . onEndControls ( ) ; } if ( this . volumeRenderMode !== RENDERMODE_PATHTRACE && ! this . canvas3d . controls . autoRotate ) { this . canvas3d . stopRenderLoop ( ) ; } this . redraw ( ) ; } buildScene ( ) { this . scene = this . canvas3d . scene ; this . oldScale = new Vector3 ( 0.5 , 0.5 , 0.5 ) ; this . currentScale = new Vector3 ( 0.5 , 0.5 , 0.5 ) ; this . canvas3d . renderer . setClearColor ( this . backgroundColor , 1.0 ) ; this . lights = [ new Light ( SKY_LIGHT ) , new Light ( AREA_LIGHT ) ] ; this . lightContainer = new Object3D ( ) ; this . lightContainer . name = "lightContainer" ; this . ambientLight = new AmbientLight ( lightSettings . ambientLightSettings . color , lightSettings . ambientLightSettings . intensity ) ; this . lightContainer . add ( this . ambientLight ) ; this . spotLight = new SpotLight ( lightSettings . spotlightSettings . color , lightSettings . spotlightSettings . intensity ) ; this . spotLight . position . set ( lightSettings . spotlightSettings . position . x , lightSettings . spotlightSettings . position . y , lightSettings . spotlightSettings . position . z ) ; this . spotLight . target = new Object3D ( ) ; this . spotLight . angle = lightSettings . spotlightSettings . angle ; this . lightContainer . add ( this . spotLight ) ; this . reflectedLight = new DirectionalLight ( lightSettings . reflectedLightSettings . color ) ; this . reflectedLight . position . set ( lightSettings . reflectedLightSettings . position . x , lightSettings . reflectedLightSettings . position . y , lightSettings . reflectedLightSettings . position . z ) ; this . reflectedLight . castShadow = lightSettings . reflectedLightSettings . castShadow ; this . reflectedLight . intensity = lightSettings . reflectedLightSettings . intensity ; this . lightContainer . add ( this . reflectedLight ) ; this . fillLight = new DirectionalLight ( lightSettings . fillLightSettings . color ) ; this . fillLight . position . set ( lightSettings . fillLightSettings . position . x , lightSettings . fillLightSettings . position . y , lightSettings . fillLightSettings . position . z ) ; this . fillLight . castShadow = lightSettings . fillLightSettings . castShadow ; this . fillLight . intensity = lightSettings . fillLightSettings . intensity ; this . lightContainer . add ( this . fillLight ) ; this . scene . add ( this . lightContainer ) ; } param { string } mode setCameraMode ( mode ) { this . canvas3d . switchViewMode ( mode ) ; if ( this . image ) { this . image . setIsOrtho ( mode !== "3D" ) ; } this . canvas3d . redraw ( ) ; } param { boolean } autorotate setShowAxis ( showAxis ) { this . canvas3d . showAxis = showAxis ; this . canvas3d . redraw ( ) ; } param { boolean } autorotate setAutoRotate ( autorotate ) { this . canvas3d . setAutoRotate ( autorotate ) ; if ( autorotate ) { this . onStartControls ( ) ; } else { this . onEndControls ( ) ; } } param { Object } volume param { number } flipX param { number } flipY param { number } flipZ setFlipVolume ( volume , flipX , flipY , flipZ ) { if ( this . image ) { this . image . setFlipAxes ( flipX , flipY , flipZ ) ; this . redraw ( ) ; } } param { HTMLElement = } comp param { number = } w param { number = } h param { number = } ow param { number = } oh param { Object = } eOpts resize ( comp , w , h , ow ? , oh ? , eOpts ? ) { w = w || this . parentEl . offsetWidth ; h = h || this . parentEl . offsetHeight ; this . canvas3d . resize ( comp , w , h , ow , oh , eOpts ) ; if ( this . image ) { this . image . setResolution ( this . canvas3d ) ; } this . redraw ( ) ; } param { Object } volume param { number } density updateDensity ( volume , density ) { if ( this . image ) { this . image . setDensity ( density ) ; } this . redraw ( ) ; } param { Object } volume param { number } isbrdf updateShadingMethod ( volume , isbrdf ) { if ( this . image ) { this . image . updateShadingMethod ( isbrdf ) ; } } param { Object } volume param { number } gmin param { number } glevel param { number } gmax setGamma ( volume , gmin , glevel , gmax ) { if ( this . image ) { this . image . setGamma ( gmin , glevel , gmax ) ; } this . redraw ( ) ; } param { Object } volume param { boolean } isMaxProject setMaxProjectMode ( volume , isMaxProject ) { if ( this . image ) { this . image . setMaxProjectMode ( isMaxProject ) ; } this . redraw ( ) ; } param { Object } volume updateActiveChannels ( _volume ) { if ( this . image ) { this . image . fuse ( ) ; } } param { Object } volume updateLuts ( _volume ) { if ( this . image ) { this . image . updateLuts ( ) ; } } param { Object } volume updateMaterial ( _volume ) { if ( this . image ) { this . image . updateMaterial ( ) ; } } param { number } e updateExposure ( e ) { this . exposure = e ; if ( this . image ) { this . image . setBrightness ( e ) ; } this . redraw ( ) ; } param { number } fov param { number } focalDistance param { number } apertureSize updateCamera ( fov , focalDistance , apertureSize ) { this . canvas3d . updateCameraFocus ( fov , focalDistance , apertureSize ) ; if ( this . image ) { this . image . onCameraChanged ( fov , focalDistance , apertureSize ) ; } this . redraw ( ) ; } param { Object } volume param { number } xmin param { number } xmax param { number } ymin param { number } ymax param { number } zmin param { number } zmax updateClipRegion ( volume , xmin , xmax , ymin , ymax , zmin , zmax ) { if ( this . image ) { this . image . updateClipRegion ( xmin , xmax , ymin , ymax , zmin , zmax ) ; } this . redraw ( ) ; } param { Object } volume param { number } axis param { number } minval param { number } maxval param { boolean } isOrthoAxis setAxisClip ( volume , axis , minval , maxval , isOrthoAxis ) { if ( this . image ) { this . image . setAxisClip ( axis , minval , maxval , isOrthoAxis ) ; } this . redraw ( ) ; } param { Array } state updateLights ( state ) { this . lights = state ; if ( this . image ) { this . image . updateLights ( state ) ; } } param { number } value updatePixelSamplingRate ( value ) { if ( this . pixelSamplingRate === value ) { return ; } this . pixelSamplingRate = value ; if ( this . image ) { this . image . setPixelSamplingRate ( value ) ; } } param { Object } volume param { number } value updateMaskAlpha ( volume , value ) { if ( this . image ) { this . image . setMaskAlpha ( value ) ; } this . redraw ( ) ; } param { Object } volume param { number } channel param { boolean } enabled setVolumeChannelEnabled ( volume , channel , enabled ) { if ( this . image ) { this . image . setVolumeChannelEnabled ( channel , enabled ) ; } } param { Object } volume param { number } channelIndex param { Array . < number > } colorrgb param { Array . < number > } specularrgb param { Array . < number > } emissivergb param { number } glossiness updateChannelMaterial ( volume , channelIndex , colorrgb , specularrgb , emissivergb , glossiness ) { if ( this . image ) { this . image . updateChannelMaterial ( channelIndex , colorrgb , specularrgb , emissivergb , glossiness ) ; } } param { Object } volume param { number } channelIndex param { Array . < number > } colorrgb updateChannelColor ( volume , channelIndex , colorrgb ) { if ( this . image ) { this . image . updateChannelColor ( channelIndex , colorrgb ) ; } } param { number } mode setVolumeRenderMode ( mode ) { if ( mode === this . volumeRenderMode ) { return ; } this . volumeRenderMode = mode ; if ( this . image ) { if ( mode === RENDERMODE_PATHTRACE && this . canvas3d . hasWebGL2 ) { this . image . setVolumeRendering ( true ) ; this . image . updateLights ( this . lights ) ; this . canvas3d . startRenderLoop ( ) ; } else { this . image . setVolumeRendering ( false ) ; this . canvas3d . redraw ( ) ; } this . updatePixelSamplingRate ( this . pixelSamplingRate ) ; this . image . setIsOrtho ( isOrthographicCamera ( this . canvas3d . camera ) ) ; this . image . setResolution ( this . canvas3d ) ; this . setAutoRotate ( this . canvas3d . controls . autoRotate ) ; this . image . setRenderUpdateListener ( this . renderUpdateListener ) ; } } param { Object } volume param { Array . < number > } xyz setVolumeTranslation ( volume , xyz ) { if ( this . image ) { this . image . setTranslation ( new Vector3 ( ) . fromArray ( xyz ) ) ; } this . redraw ( ) ; } param { Object } volume param { Array . < number > } eulerXYZ setVolumeRotation ( volume , eulerXYZ ) { if ( this . image ) { this . image . setRotation ( new Euler ( ) . fromArray ( eulerXYZ ) ) ; } this . redraw ( ) ; } /**
     * Reset the camera to its default position
     */ resetCamera ( ) { this . canvas3d . resetCamera ( ) ; if ( this . image ) { this . image . onResetCamera ( ) ; } this . redraw ( ) ; } hasWebGL2 ( ) { return this . canvas3d . hasWebGL2 ; } }