import { Vector3 , Object3D , Euler } from "three" ; import MeshVolume from "./MeshVolume" ; import RayMarchedAtlasVolume from "./RayMarchedAtlasVolume" ; import PathTracedVolume from "./PathTracedVolume" ; import { LUT_ARRAY_LENGTH } from "./Histogram" ; import Volume from "./Volume" ; import { VolumeDisplayOptions , VolumeChannelDisplayOptions } from "./types" ; import { Bounds , FuseChannel } from "./types" ; import { ThreeJsPanel } from "./ThreeJsPanel" ; import { Light } from "./Light" ; import Channel from "./Channel" ; export default class VolumeDrawable { public PT : boolean ; public volume : Volume ; private onChannelDataReadyCallback ? : ( ) => void ; private translation : Vector3 ; private rotation : Euler ; private flipX : number ; private flipY : number ; private flipZ : number ; private maskChannelIndex : number ; private maskAlpha : number ; private gammaMin : number ; private gammaLevel : number ; private gammaMax : number ; private channelColors : [ number , number , number ] [ ] ; private channelOptions : VolumeChannelDisplayOptions [ ] ; private fusion : FuseChannel [ ] ; public specular : [ number , number , number ] [ ] ; public emissive : [ number , number , number ] [ ] ; public glossiness : number [ ] ; public sceneRoot : Object3D ; private meshVolume : MeshVolume ; private primaryRayStepSize : number ; private secondaryRayStepSize : number ; private showBoundingBox : boolean ; private boundingBoxColor : [ number , number , number ] ; private pathTracedVolume ? : PathTracedVolume ; private rayMarchedAtlasVolume ? : RayMarchedAtlasVolume ; private volumeRendering : PathTracedVolume | RayMarchedAtlasVolume ; private bounds : Bounds ; private scale : Vector3 ; private currentScale : Vector3 ; private renderUpdateListener ? : ( number ) => void ; private density : number ; private brightness : number ; constructor ( volume , options ) { this . PT = ! ! options . renderMode ; this . volume = volume ; this . onChannelDataReadyCallback = undefined ; this . translation = new Vector3 ( 0 , 0 , 0 ) ; this . rotation = new Euler ( ) ; this . flipX = 1 ; this . flipY = 1 ; this . flipZ = 1 ; this . maskChannelIndex = - 1 ; this . maskAlpha = 1.0 ; this . gammaMin = 0.0 ; this . gammaLevel = 1.0 ; this . gammaMax = 1.0 ; this . density = 0 ; this . brightness = 0 ; this . showBoundingBox = false ; this . boundingBoxColor = [ 1.0 , 1.0 , 0.0 ] ; this . channelColors = this . volume . channel_colors_default . slice ( ) ; this . channelOptions = new Array ( this . volume . num_channels ) . fill ( { } ) ; this . fusion = this . channelColors . map ( ( col , index ) => { let rgbColor ; if ( col [ 0 ] === 0 && col [ 1 ] === 0 && col [ 2 ] === 0 ) { rgbColor = 0 ; } else { rgbColor = [ col [ 0 ] , col [ 1 ] , col [ 2 ] ] ; } return { chIndex : index , lut : new Uint8Array ( LUT_ARRAY_LENGTH ) , rgbColor : rgbColor , } ; } ) ; this . specular = new Array ( this . volume . num_channels ) . fill ( [ 0 , 0 , 0 ] ) ; this . emissive = new Array ( this . volume . num_channels ) . fill ( [ 0 , 0 , 0 ] ) ; this . glossiness = new Array ( this . volume . num_channels ) . fill ( 0 ) ; this . sceneRoot = new Object3D ( ) ; this . meshVolume = new MeshVolume ( this . volume ) ; this . primaryRayStepSize = 1.0 ; this . secondaryRayStepSize = 1.0 ; if ( this . PT ) { this . volumeRendering = new PathTracedVolume ( this . volume ) ; this . pathTracedVolume = this . volumeRendering ; } else { this . volumeRendering = new RayMarchedAtlasVolume ( this . volume ) ; this . rayMarchedAtlasVolume = this . volumeRendering ; } ! this . PT && this . sceneRoot . add ( this . meshVolume . get3dObject ( ) ) ; this . sceneRoot . add ( this . volumeRendering . get3dObject ( ) ) ; this . bounds = { bmin : new Vector3 ( - 0.5 , - 0.5 , - 0.5 ) , bmax : new Vector3 ( 0.5 , 0.5 , 0.5 ) , } ; this . sceneRoot . position . set ( 0 , 0 , 0 ) ; this . scale = new Vector3 ( 1 , 1 , 1 ) ; this . currentScale = new Vector3 ( 1 , 1 , 1 ) ; this . setScale ( this . volume . scale ) ; this . setTranslation ( new Vector3 ( ) . fromArray ( this . volume . getTranslation ( ) ) ) ; this . setRotation ( new Euler ( ) . fromArray ( this . volume . getRotation ( ) ) ) ; this . setOptions ( options ) ; } setOptions ( options ) { options = options || { } ; if ( options . maskChannelIndex !== undefined ) { this . setChannelAsMask ( options . maskChannelIndex ) ; } if ( options . maskAlpha !== undefined ) { this . setMaskAlpha ( options . maskAlpha ) ; } if ( options . clipBounds !== undefined ) { this . bounds = { bmin : new Vector3 ( options . clipBounds [ 0 ] , options . clipBounds [ 2 ] , options . clipBounds [ 4 ] ) , bmax : new Vector3 ( options . clipBounds [ 1 ] , options . clipBounds [ 3 ] , options . clipBounds [ 5 ] ) , } ; this . setAxisClip ( 0 , options . clipBounds [ 0 ] , options . clipBounds [ 1 ] ) ; this . setAxisClip ( 1 , options . clipBounds [ 2 ] , options . clipBounds [ 3 ] ) ; this . setAxisClip ( 2 , options . clipBounds [ 4 ] , options . clipBounds [ 5 ] ) ; } if ( options . scale !== undefined ) { this . setScale ( new Vector3 ( ) . fromArray ( options . scale ) ) ; } if ( options . translation !== undefined ) { this . setTranslation ( new Vector3 ( ) . fromArray ( options . translation ) ) ; } if ( options . rotation !== undefined ) { this . setRotation ( new Euler ( ) . fromArray ( options . rotation ) ) ; } if ( options . renderMode !== undefined ) { this . setVolumeRendering ( ! ! options . renderMode ) ; } if ( options . primaryRayStepSize !== undefined || options . secondaryRayStepSize !== undefined ) { this . setRayStepSizes ( options . primaryRayStepSize , options . secondaryRayStepSize ) ; } if ( options . showBoundingBox !== undefined ) { this . setShowBoundingBox ( options . showBoundingBox ) ; } if ( options . boundingBoxColor !== undefined ) { this . setBoundingBoxColor ( options . boundingBoxColor ) ; } if ( options . channels !== undefined ) { this . channelOptions = options . channels ; this . channelOptions . forEach ( ( channelOptions , channelIndex ) => { this . setChannelOptions ( channelIndex , channelOptions ) ; } ) ; } } setChannelOptions ( channelIndex , options ) { this . channelOptions [ channelIndex ] = Object . assign ( this . channelOptions [ channelIndex ] , options ) ; if ( Object . hasOwnProperty . call ( options , "enabled" ) ) { this . setVolumeChannelEnabled ( channelIndex , options . enabled ) ; } if ( Object . hasOwnProperty . call ( options , "color" ) ) { this . updateChannelColor ( channelIndex , options . color ) ; } if ( Object . hasOwnProperty . call ( options , "isosurfaceEnabled" ) ) { const hasIso = this . hasIsosurface ( channelIndex ) ; if ( hasIso !== options . isosurfaceEnabled ) { if ( hasIso && ! options . isosurfaceEnabled ) { this . destroyIsosurface ( channelIndex ) ; } else if ( ! hasIso && options . isosurfaceEnabled ) { let isovalue = 127 ; if ( Object . hasOwnProperty . call ( options , "isovalue" ) ) { isovalue = options . isovalue ; } let isosurfaceOpacity = 1.0 ; if ( Object . hasOwnProperty . call ( options , "isosurfaceOpacity" ) ) { isosurfaceOpacity = options . isosurfaceOpacity ; } this . createIsosurface ( channelIndex , isovalue , isosurfaceOpacity , isosurfaceOpacity < 1.0 ) ; } } else if ( options . isosurfaceEnabled ) { if ( Object . hasOwnProperty . call ( options , "isovalue" ) ) { this . updateIsovalue ( channelIndex , options . isovalue ) ; } if ( Object . hasOwnProperty . call ( options , "isosurfaceOpacity" ) ) { this . updateOpacity ( channelIndex , options . isosurfaceOpacity ) ; } } } else { if ( Object . hasOwnProperty . call ( options , "isovalue" ) ) { this . updateIsovalue ( channelIndex , options . isovalue ) ; } if ( Object . hasOwnProperty . call ( options , "isosurfaceOpacity" ) ) { this . updateOpacity ( channelIndex , options . isosurfaceOpacity ) ; } } } setRayStepSizes ( primary ? , secondary ? ) { if ( primary !== undefined ) { this . primaryRayStepSize = primary ; } if ( secondary !== undefined ) { this . secondaryRayStepSize = secondary ; } this . volumeRendering . setRayStepSizes ( this . primaryRayStepSize , this . secondaryRayStepSize ) ; } setScale ( scale ) { this . scale = scale ; this . currentScale = scale . clone ( ) ; this . meshVolume . setScale ( scale ) ; this . volumeRendering . setScale ( scale ) ; } setOrthoScale ( value ) { this . volumeRendering . setOrthoScale ( value ) ; } setResolution ( viewObj ) { const x = viewObj . getWidth ( ) ; const y = viewObj . getHeight ( ) ; this . volumeRendering . setResolution ( x , y ) ; this . meshVolume . setResolution ( x , y ) ; } setAxisClip ( axis , minval , maxval , isOrthoAxis ? ) { this . bounds . bmax [ axis ] = maxval ; this . bounds . bmin [ axis ] = minval ; ! this . PT && this . meshVolume . setAxisClip ( axis , minval , maxval , ! ! isOrthoAxis ) ; this . volumeRendering . setAxisClip ( axis , minval , maxval , isOrthoAxis || false ) ; } setIsOrtho ( isOrtho ) { this . volumeRendering . setIsOrtho ( isOrtho ) ; } setOrthoThickness ( value ) { ! this . PT && this . meshVolume . setOrthoThickness ( value ) ; this . volumeRendering . setOrthoThickness ( value ) ; } setGamma ( gmin , glevel , gmax ) { this . gammaMin = gmin ; this . gammaLevel = glevel ; this . gammaMax = gmax ; this . volumeRendering . setGamma ( gmin , glevel , gmax ) ; } setFlipAxes ( flipX , flipY , flipZ ) { this . flipX = flipX ; this . flipY = flipY ; this . flipZ = flipZ ; this . volumeRendering . setFlipAxes ( flipX , flipY , flipZ ) ; this . meshVolume . setFlipAxes ( flipX , flipY , flipZ ) ; } setMaxProjectMode ( isMaxProject ) { ! this . PT && this . rayMarchedAtlasVolume && this . rayMarchedAtlasVolume . setMaxProjectMode ( isMaxProject ) ; } onAnimate ( canvas ) { canvas . camera . updateMatrixWorld ( true ) ; canvas . camera . matrixWorldInverse . copy ( canvas . camera . matrixWorld ) . invert ( ) ; this . volumeRendering . doRender ( canvas ) ; ! this . PT && this . meshVolume . doRender ( canvas ) ; } updateIsovalue ( channel , value ) { this . meshVolume . updateIsovalue ( channel , value ) ; } getIsovalue ( channel ) { return this . meshVolume . getIsovalue ( channel ) ; } updateOpacity ( channel , value ) { this . meshVolume . updateOpacity ( channel , value ) ; } hasIsosurface ( channel ) { return this . meshVolume . hasIsosurface ( channel ) ; } createIsosurface ( channel , value , alpha , transp ) { this . meshVolume . createIsosurface ( channel , this . channelColors [ channel ] , value , alpha , transp ) ; } destroyIsosurface ( channel ) { this . meshVolume . destroyIsosurface ( channel ) ; } fuse ( ) { if ( ! this . volume ) { return ; } if ( this . PT ) { if ( this . pathTracedVolume ) { this . pathTracedVolume . updateActiveChannels ( this ) ; } } else { if ( this . rayMarchedAtlasVolume ) { this . rayMarchedAtlasVolume . fuse ( this . fusion , this . volume . channels ) ; } } } setRenderUpdateListener ( callback ? ) { this . renderUpdateListener = callback ; if ( this . PT && this . pathTracedVolume ) { this . pathTracedVolume . setRenderUpdateListener ( callback ) ; } } updateShadingMethod ( isbrdf ) { if ( this . PT && this . pathTracedVolume ) { this . pathTracedVolume . updateShadingMethod ( isbrdf ? 1 : 0 ) ; } } updateMaterial ( ) { this . PT && this . pathTracedVolume && this . pathTracedVolume . updateMaterial ( this ) ; ! this . PT && this . rayMarchedAtlasVolume && this . rayMarchedAtlasVolume . fuse ( this . fusion , this . volume . channels ) ; } updateLuts ( ) { this . PT && this . pathTracedVolume && this . pathTracedVolume . updateLuts ( this ) ; ! this . PT && this . rayMarchedAtlasVolume && this . rayMarchedAtlasVolume . fuse ( this . fusion , this . volume . channels ) ; } setVoxelSize ( values ) { this . volume . setVoxelSize ( values ) ; this . setScale ( this . volume . scale ) ; } cleanup ( ) { this . meshVolume . cleanup ( ) ; this . volumeRendering . cleanup ( ) ; } getChannel ( channelIndex ) { return this . volume . getChannel ( channelIndex ) ; } onChannelLoaded ( batch ) { this . volumeRendering . onChannelData ( batch ) ; this . meshVolume . onChannelData ( batch ) ; for ( let j = 0 ; j < batch . length ; ++ j ) { const idx = batch [ j ] ; this . setChannelOptions ( idx , this . channelOptions [ idx ] ) ; } if ( this . onChannelDataReadyCallback ) { this . onChannelDataReadyCallback ( ) ; } } onChannelAdded ( newChannelIndex ) { this . channelColors [ newChannelIndex ] = this . volume . channel_colors_default [ newChannelIndex ] ; this . fusion [ newChannelIndex ] = { chIndex : newChannelIndex , lut : new Uint8Array [ LUT_ARRAY_LENGTH ] ( ) , rgbColor : [ this . channelColors [ newChannelIndex ] [ 0 ] , this . channelColors [ newChannelIndex ] [ 1 ] , this . channelColors [ newChannelIndex ] [ 2 ] , ] , } ; this . specular [ newChannelIndex ] = [ 0 , 0 , 0 ] ; this . emissive [ newChannelIndex ] = [ 0 , 0 , 0 ] ; this . glossiness [ newChannelIndex ] = 0 ; } saveChannelIsosurface ( channelIndex , type ) { this . meshVolume . saveChannelIsosurface ( channelIndex , type , this . volume . name ) ; } setVolumeChannelEnabled ( channelIndex , enabled ) { this . fusion [ channelIndex ] . rgbColor = enabled ? this . channelColors [ channelIndex ] : 0 ; if ( this . fusion . every ( ( elem ) => elem . rgbColor === 0 ) ) { this . volumeRendering . setVisible ( false ) ; } else { this . volumeRendering . setVisible ( true ) ; } } isVolumeChannelEnabled ( channelIndex ) { return this . fusion [ channelIndex ] . rgbColor !== 0 ; } updateChannelColor ( channelIndex , colorrgb ) { if ( ! this . channelColors [ channelIndex ] ) { return ; } this . channelColors [ channelIndex ] = colorrgb ; if ( this . fusion [ channelIndex ] . rgbColor !== 0 ) { this . fusion [ channelIndex ] . rgbColor = colorrgb ; } this . meshVolume . updateMeshColors ( this . channelColors ) ; } updateMeshColors ( ) { this . meshVolume . updateMeshColors ( this . channelColors ) ; } getChannelColor ( channelIndex ) { return this . channelColors [ channelIndex ] ; } updateChannelMaterial ( channelIndex , colorrgb , specularrgb , emissivergb , glossiness ) { if ( ! this . channelColors [ channelIndex ] ) { return ; } this . updateChannelColor ( channelIndex , colorrgb ) ; this . specular [ channelIndex ] = specularrgb ; this . emissive [ channelIndex ] = emissivergb ; this . glossiness [ channelIndex ] = glossiness ; } setDensity ( density ) { this . density = density ; this . volumeRendering . setDensity ( density ) ; } /**
     * Get the global density of the volume data
     */ getDensity ( ) { return this . density ; } setBrightness ( brightness ) { this . brightness = brightness ; this . volumeRendering . setBrightness ( brightness ) ; } getBrightness ( ) { return this . brightness ; } setChannelAsMask ( channelIndex ) { if ( ! this . volume . channels [ channelIndex ] || ! this . volume . channels [ channelIndex ] . loaded ) { return false ; } this . maskChannelIndex = channelIndex ; return this . volumeRendering . setChannelAsMask ( channelIndex ) ; } setMaskAlpha ( maskAlpha ) { this . maskAlpha = maskAlpha ; this . volumeRendering . setMaskAlpha ( maskAlpha ) ; } setShowBoundingBox ( showBoundingBox ) { this . showBoundingBox = showBoundingBox ; this . volumeRendering . setShowBoundingBox ( showBoundingBox ) ; } setBoundingBoxColor ( color ) { this . boundingBoxColor = color ; this . volumeRendering . setBoundingBoxColor ( color ) ; } getIntensity ( c , x , y , z ) { return this . volume . getIntensity ( c , x , y , z ) ; } onStartControls ( ) { this . PT && this . pathTracedVolume && this . pathTracedVolume . onStartControls ( ) ; } onChangeControls ( ) { this . PT && this . pathTracedVolume && this . pathTracedVolume . onChangeControls ( ) ; } onEndControls ( ) { this . PT && this . pathTracedVolume && this . pathTracedVolume . onEndControls ( ) ; } onResetCamera ( ) { this . volumeRendering . viewpointMoved ( ) ; } onCameraChanged ( fov , focalDistance , apertureSize ) { this . PT && this . pathTracedVolume && this . pathTracedVolume . updateCamera ( fov , focalDistance , apertureSize ) ; } updateClipRegion ( xmin , xmax , ymin , ymax , zmin , zmax ) { this . bounds . bmin = new Vector3 ( xmin - 0.5 , ymin - 0.5 , zmin - 0.5 ) ; this . bounds . bmax = new Vector3 ( xmax - 0.5 , ymax - 0.5 , zmax - 0.5 ) ; this . volumeRendering . updateClipRegion ( xmin , xmax , ymin , ymax , zmin , zmax ) ; this . meshVolume . updateClipRegion ( xmin , xmax , ymin , ymax , zmin , zmax ) ; } updateLights ( state ) { this . PT && this . pathTracedVolume && this . pathTracedVolume . updateLights ( state ) ; } setPixelSamplingRate ( value ) { this . volumeRendering . setPixelSamplingRate ( value ) ; } setVolumeRendering ( isPathtrace ) { if ( isPathtrace === this . PT ) { return ; } isPathtrace && this . sceneRoot . remove ( this . meshVolume . get3dObject ( ) ) ; this . sceneRoot . remove ( this . volumeRendering . get3dObject ( ) ) ; this . volumeRendering . cleanup ( ) ; if ( isPathtrace ) { this . volumeRendering = new PathTracedVolume ( this . volume ) ; this . pathTracedVolume = this . volumeRendering ; this . rayMarchedAtlasVolume = undefined ; this . volumeRendering . setRenderUpdateListener ( this . renderUpdateListener ) ; } else { this . volumeRendering = new RayMarchedAtlasVolume ( this . volume ) ; this . pathTracedVolume = undefined ; this . rayMarchedAtlasVolume = this . volumeRendering ; for ( let i = 0 ; i < this . volume . num_channels ; ++ i ) { if ( this . volume . getChannel ( i ) . loaded ) { this . rayMarchedAtlasVolume . onChannelData ( [ i ] ) ; } } if ( this . renderUpdateListener ) { this . renderUpdateListener ( 0 ) ; } } this . volumeRendering . setTranslation ( this . translation ) ; this . volumeRendering . setRotation ( this . rotation ) ; this . PT = isPathtrace ; this . setChannelAsMask ( this . maskChannelIndex ) ; this . setMaskAlpha ( this . maskAlpha ) ; this . setScale ( this . volume . scale ) ; this . setBrightness ( this . getBrightness ( ) ) ; this . setDensity ( this . getDensity ( ) ) ; this . setGamma ( this . gammaMin , this . gammaLevel , this . gammaMax ) ; this . setFlipAxes ( this . flipX , this . flipY , this . flipZ ) ; this . setAxisClip ( 0 , this . bounds . bmin . x , this . bounds . bmax . x ) ; this . setAxisClip ( 1 , this . bounds . bmin . y , this . bounds . bmax . y ) ; this . setAxisClip ( 2 , this . bounds . bmin . z , this . bounds . bmax . z ) ; this . updateClipRegion ( this . bounds . bmin . x + 0.5 , this . bounds . bmax . x + 0.5 , this . bounds . bmin . y + 0.5 , this . bounds . bmax . y + 0.5 , this . bounds . bmin . z + 0.5 , this . bounds . bmax . z + 0.5 ) ; this . setRayStepSizes ( this . primaryRayStepSize , this . secondaryRayStepSize ) ; this . setShowBoundingBox ( this . showBoundingBox ) ; this . setBoundingBoxColor ( this . boundingBoxColor ) ; ! this . PT && this . sceneRoot . add ( this . meshVolume . get3dObject ( ) ) ; this . sceneRoot . add ( this . volumeRendering . get3dObject ( ) ) ; this . fuse ( ) ; } setTranslation ( xyz ) { this . translation . copy ( xyz ) ; this . volumeRendering . setTranslation ( this . translation ) ; this . meshVolume . setTranslation ( this . translation ) ; } setRotation ( eulerXYZ ) { this . rotation . copy ( eulerXYZ ) ; this . volumeRendering . setRotation ( this . rotation ) ; this . meshVolume . setRotation ( this . rotation ) ; } }