import { Designer } from "./designer" ; import { DesignerNode } from "./designer/designernode" ; import { Property , PropertyType } from "./designer/properties" ; import { Guid } from "./utils" ; import { DesignerVariableType } from "./designer/designervariable" ; import { DesignerLibrary } from "./designer/library" ; import { NodeScene } from "./scene" ; import { ConnectionGraphicsItem } from "./scene/connectiongraphicsitem" ; import { NodeGraphicsItem } from "./scene/nodegraphicsitem" ; import { SocketType } from "./scene/socketgraphicsitem" ; import { ImageCanvas } from "./designer/imagecanvas" ; import { createLibrary , createLibrary as createV1Library } from "@/lib/library/libraryv1" ; import { createLibrary as createV2Library } from "@/lib/library/libraryv2" ; import { Color } from "./designer/color" ; import { CommentGraphicsItem } from "./scene/commentgraphicsitem" ; import { FrameGraphicsItem } from "./scene/framegraphicsitem" ; import { NavigationGraphicsItem } from "./scene/navigationgraphicsitem" ; import { ItemClipboard } from "./clipboard" ; import { UndoStack } from "./undostack" ; import { RemoveItemsAction } from "./actions/removeitemsaction" ; function hexToRgb ( hex ) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i . exec ( hex ) ; return result ? { r : parseInt ( result [ 1 ] , 16 ) , g : parseInt ( result [ 2 ] , 16 ) , b : parseInt ( result [ 3 ] , 16 ) } : null ; } function rgbToHex ( r , g , b ) { return "#" + ( ( 1 << 24 ) + ( r << 16 ) + ( g << 8 ) + b ) . toString ( 16 ) . slice ( 1 ) ; } export class DisplayNodes { public albedoNode : string ; public normalNode : string ; public roughnessNode : string ; public heightNode : string ; public metallicNode : string ; } export enum DisplayChannel { Albedo , Metallic , Roughness , Normal , Height } export class Editor { static active : Editor ; canvas : HTMLCanvasElement ; library : DesignerLibrary ; graph : NodeScene ; designer : Designer ; selectedDesignerNode : DesignerNode ; undoStack : UndoStack ; preview2D : HTMLCanvasElement ; preview2DCtx : CanvasRenderingContext2D ; scene3D : any ; displayNodes : DisplayNodes ; onnodeselected ? : ( item : DesignerNode ) => void ; oncommentselected ? : ( item : CommentGraphicsItem ) => void ; onframeselected ? : ( item : FrameGraphicsItem ) => void ; onnavigationselected ? : ( item : NavigationGraphicsItem ) => void ; onpreviewnode ? : ( item : DesignerNode , image : HTMLCanvasElement ) => void ; onlibrarymenu ? : ( ) => void ; textureChannels : Map < string , DesignerNode > = new Map < string , DesignerNode > ( ) ; ontexturechannelcleared ? : ( imageCanvas : ImageCanvas , channelName : string ) => void ; ontexturechannelassigned ? : ( imageCanvas : ImageCanvas , channelName : string ) => void ; ontexturechannelupdated ? : ( imageCanvas : ImageCanvas , channelName : string ) => void ; constructor ( ) { this . displayNodes = new DisplayNodes ( ) ; this . selectedDesignerNode = null ; this . undoStack = new UndoStack ( ) ; } getImageWidth ( ) { return this . designer . width ; } getImageHeight ( ) { return this . designer . height ; } assignNodeToTextureChannel ( nodeId , channelName ) { if ( this . textureChannels . has ( channelName ) && this . textureChannels . get ( channelName ) ) { const oldNode = this . textureChannels . get ( channelName ) ; const nodeView = this . graph . getNodeById ( oldNode . id ) ; nodeView . clearTextureChannel ( ) ; this . textureChannels . delete ( channelName ) ; if ( this . ontexturechannelcleared ) { this . ontexturechannelcleared ( null , channelName ) ; } } const nodeView = this . graph . getNodeById ( nodeId ) ; nodeView . setTextureChannel ( channelName ) ; const newNode = this . designer . getNodeById ( nodeId ) ; this . textureChannels . set ( channelName , newNode ) ; if ( this . ontexturechannelcleared ) { this . ontexturechannelassigned ( nodeView . imageCanvas , channelName ) ; } } clearTextureChannel ( nodeId ) { for ( const channelName of this . textureChannels . keys ( ) ) { const node = this . textureChannels . get ( channelName ) ; if ( node . id == nodeId ) { const oldNode = this . textureChannels . get ( channelName ) ; const nodeView = this . graph . getNodeById ( oldNode . id ) ; if ( nodeView ) nodeView . clearTextureChannel ( ) ; this . textureChannels . delete ( channelName ) ; if ( this . ontexturechannelcleared ) { this . ontexturechannelcleared ( null , channelName ) ; } } } } hasTextureChannel ( channelName ) { return this . textureChannels . has ( channelName ) ; } clearTextureChannels ( ) { for ( const channelName of this . textureChannels . keys ( ) ) { const node = this . textureChannels . get ( channelName ) ; this . clearTextureChannel ( node . id ) ; } } getChannelCanvasImage ( channelName ) { if ( this . hasTextureChannel ( channelName ) ) { const dnodeId = this . textureChannels . get ( channelName ) . id ; const nodeView = this . graph . getNodeById ( dnodeId ) ; return nodeView . imageCanvas ; } return null ; } undo ( ) { this . undoStack . undo ( ) ; } redo ( ) { this . undoStack . redo ( ) ; } createNewTexture ( ) { this . clearTextureChannels ( ) ; this . library = createV2Library ( ) ; this . setDesigner ( new Designer ( ) ) ; this . setScene ( new NodeScene ( this . canvas ) ) ; this . setupDefaultScene ( ) ; } setupDefaultScene ( ) { const offset = 100 ; const spacing = 150 ; let node = this . library . create ( "output" ) ; let nodeView = this . addNode ( node , 0 , 0 ) ; node . setProperty ( "name" , "albedo" ) ; node . setProperty ( "color" , new Color ( 1 , 1 , 1 , 1 ) ) ; node . setProperty ( "components" , 0 ) ; nodeView . setCenter ( 800 , offset + spacing * 0 ) ; this . assignNodeToTextureChannel ( nodeView . id , "albedo" ) ; node = this . library . create ( "output" ) ; nodeView = this . addNode ( node , 0 , 0 ) ; node . setProperty ( "name" , "normal" ) ; node . setProperty ( "color" , new Color ( 0.5 , 0.5 , 1 , 1 ) ) ; node . setProperty ( "components" , 1 ) ; nodeView . setCenter ( 800 , offset + spacing * 1 ) ; this . assignNodeToTextureChannel ( nodeView . id , "normal" ) ; const normalId = node . id ; node = this . library . create ( "normalmap" ) ; nodeView = this . addNode ( node , 0 , 0 ) ; nodeView . setCenter ( 600 , offset + spacing * 1 ) ; this . graph . createConnection ( node . id , normalId , 0 ) ; node = this . library . create ( "output" ) ; nodeView = this . addNode ( node , 0 , 0 ) ; node . setProperty ( "name" , "roughness" ) ; node . setProperty ( "color" , new Color ( 0.5 , 0.5 , 0.5 , 1 ) ) ; node . setProperty ( "components" , 2 ) ; node . setProperty ( "precision" , 1 ) ; nodeView . setCenter ( 800 , offset + spacing * 2 ) ; this . assignNodeToTextureChannel ( nodeView . id , "roughness" ) ; node = this . library . create ( "output" ) ; nodeView = this . addNode ( node , 0 , 0 ) ; node . setProperty ( "name" , "metalness" ) ; node . setProperty ( "color" , new Color ( 0 , 0 , 0 , 1 ) ) ; node . setProperty ( "components" , 2 ) ; node . setProperty ( "precision" , 1 ) ; nodeView . setCenter ( 800 , offset + spacing * 3 ) ; this . assignNodeToTextureChannel ( nodeView . id , "metalness" ) ; node = this . library . create ( "output" ) ; nodeView = this . addNode ( node , 0 , 0 ) ; node . setProperty ( "name" , "height" ) ; node . setProperty ( "color" , new Color ( 0 , 0 , 0 , 1 ) ) ; node . setProperty ( "components" , 2 ) ; node . setProperty ( "precision" , 1 ) ; nodeView . setCenter ( 800 , offset + spacing * 4 ) ; this . assignNodeToTextureChannel ( nodeView . id , "height" ) ; this . designer . invalidateAllNodes ( ) ; } set2DPreview ( preview2D ) { this . preview2D = preview2D ; this . preview2DCtx = preview2D . getContext ( "2d" ) ; } setSceneCanvas ( canvas ) { this . canvas = canvas ; this . setScene ( new NodeScene ( canvas ) ) ; } resizeScene ( width , height ) { this . canvas . width = width ; this . canvas . height = height ; } set3DScene ( scene3D ) { this . scene3D = scene3D ; } setDesigner ( designer ) { this . designer = designer ; const self = this ; designer . onnodetextureupdated = function ( dnode , dtInMs ) { const graphNode = self . graph . getNodeById ( dnode . id ) ; if ( ! graphNode ) return ; graphNode . processingTime = dtInMs ; self . designer . copyNodeTextureToImageCanvas ( dnode , graphNode . imageCanvas ) ; if ( self . onpreviewnode ) { if ( dnode == self . selectedDesignerNode ) self . onpreviewnode ( dnode , graphNode . imageCanvas . canvas ) ; } if ( self . ontexturechannelupdated && graphNode . textureChannel ) { self . ontexturechannelupdated ( graphNode . imageCanvas , graphNode . textureChannel ) ; } self . updateDisplayNode ( graphNode ) ; } ; designer . onnodetimeupdated = function ( dnode , dtInMs ) { const graphNode = self . graph . getNodeById ( dnode . id ) ; if ( ! graphNode ) return ; graphNode . processingTime = dtInMs ; } ; } setScene ( scene ) { if ( this . graph ) this . graph . dispose ( ) ; this . undoStack . clear ( ) ; this . graph = scene ; const self = this ; this . graph . onconnectioncreated = function ( con ) { const leftNode = con . socketA . node ; const rightNode = con . socketB . node ; const leftDNode = self . designer . getNodeById ( leftNode . id ) ; const rightDNode = self . designer . getNodeById ( rightNode . id ) ; self . designer . addConnection ( leftDNode , rightDNode , con . socketB . title ) ; } ; this . graph . onconnectiondestroyed = function ( con ) { const leftNode = con . socketA . node ; const rightNode = con . socketB . node ; const leftDNode = self . designer . getNodeById ( leftNode . id ) ; const rightDNode = self . designer . getNodeById ( rightNode . id ) ; self . designer . removeConnection ( leftDNode , rightDNode , con . socketB . title ) ; rightNode . setThumbnail ( null ) ; } ; this . graph . onnodeselected = function ( node ) { if ( node != null ) { var dnode = self . designer . getNodeById ( node . id ) ; self . selectedDesignerNode = dnode ; if ( self . preview2DCtx ) { self . preview2DCtx . drawImage ( node . imageCanvas . canvas , 0 , 0 , self . preview2D . width , self . preview2D . height ) ; } if ( self . onpreviewnode ) { self . onpreviewnode ( dnode , node . imageCanvas . canvas ) ; } if ( self . scene3D ) { self . updateDisplayNode ( node ) ; } } if ( self . onnodeselected ) self . onnodeselected ( dnode ) ; } ; this . graph . oncommentselected = function ( item ) { if ( self . oncommentselected ) self . oncommentselected ( item ) ; } ; this . graph . onframeselected = function ( item ) { if ( self . onframeselected ) self . onframeselected ( item ) ; } ; this . graph . onnavigationselected = function ( item ) { if ( self . onnavigationselected ) self . onnavigationselected ( item ) ; } ; this . graph . onnodedeleted = function ( node ) { self . clearTextureChannel ( node . id ) ; self . designer . removeNode ( node . id ) ; if ( self . onpreviewnode ) { self . onpreviewnode ( null , null ) ; } } ; this . graph . onitemsdeleting = function ( frames , comments , navs , cons , nodes ) { const dnodes = [ ] ; for ( const node of nodes ) { const dnode = self . designer . getNodeById ( node . id ) ; if ( dnode == null ) throw "Node with id " + dnode . id + " doesnt exist!!" ; dnodes . push ( dnode ) ; } const action = new RemoveItemsAction ( self , self . graph , self . designer , frames , comments , navs , cons , nodes , dnodes ) ; UndoStack . current . push ( action ) ; } ; this . graph . onitemsdeleted = function ( frames , comments , navs , cons , nodes ) { if ( self . onpreviewnode ) { self . onpreviewnode ( null , null ) ; } } ; this . graph . oncopy = function ( evt ) { self . executeCopy ( evt ) ; } ; this . graph . oncut = function ( evt ) { self . executeCut ( evt ) ; } ; this . graph . onpaste = function ( evt ) { self . executePaste ( evt ) ; } ; this . graph . onlibrarymenu = function ( ) { if ( self . onlibrarymenu != null ) { self . onlibrarymenu ( ) ; } } ; } executeCopy ( evt ) { ItemClipboard . copyItems ( this . designer , this . library , this . graph , evt . clipboardData ) ; } executeCut ( evt ) { ItemClipboard . copyItems ( this . designer , this . library , this . graph , evt . clipboardData ) ; } executePaste ( evt ) { ItemClipboard . pasteItems ( this . designer , this . library , this . graph , evt . clipboardData ) ; } addNode ( dNode , screenX = 0 , screenY = 0 ) { this . designer . addNode ( dNode ) ; const node = new NodeGraphicsItem ( dNode . title ) ; for ( const input of dNode . getInputs ( ) ) { node . addSocket ( input , input , SocketType . In ) ; } node . addSocket ( "output" , "output" , SocketType . Out ) ; this . graph . addNode ( node ) ; node . id = dNode . id ; const thumb = this . designer . generateImageFromNode ( dNode ) ; node . setThumbnail ( thumb ) ; const pos = this . graph . view . canvasToSceneXY ( screenX , screenY ) ; node . setCenter ( pos . x , pos . y ) ; return node ; } createComment ( ) { const comment = new CommentGraphicsItem ( this . graph . view ) ; const pos = this . graph . view . sceneCenter ; comment . setCenter ( pos . x , pos . y ) ; this . graph . addComment ( comment ) ; return comment ; } createFrame ( ) { const frame = new FrameGraphicsItem ( this . graph . view ) ; const pos = this . graph . view . sceneCenter ; frame . setCenter ( pos . x , pos . y ) ; this . graph . addFrame ( frame ) ; return frame ; } createNavigation ( ) { const nav = new NavigationGraphicsItem ( ) ; const pos = this . graph . view . sceneCenter ; nav . setCenter ( pos . x , pos . y ) ; this . graph . addNavigation ( nav ) ; return nav ; } updateDisplayNode ( node ) { if ( ! this . scene3D ) return ; if ( node . id == this . displayNodes . albedoNode ) { this . scene3D . setAlbedoCanvasTexture ( node . imageCanvas . canvas ) ; } if ( node . id == this . displayNodes . metallicNode ) { this . scene3D . setMetallicCanvasTexture ( node . imageCanvas . canvas ) ; } if ( node . id == this . displayNodes . normalNode ) { this . scene3D . setNormalCanvasTexture ( node . imageCanvas . canvas ) ; } if ( node . id == this . displayNodes . roughnessNode ) { this . scene3D . setRoughnessCanvasTexture ( node . imageCanvas . canvas ) ; } if ( node . id == this . displayNodes . heightNode ) { this . scene3D . setHeightCanvasTexture ( node . imageCanvas . canvas ) ; } } setDisplayChannelNode ( channel , nodeId ) { const node = this . graph . getNodeById ( nodeId ) ; if ( channel == DisplayChannel . Albedo ) { this . displayNodes . albedoNode = nodeId ; } if ( channel == DisplayChannel . Metallic ) { this . displayNodes . metallicNode = nodeId ; } if ( channel == DisplayChannel . Normal ) { this . displayNodes . normalNode = nodeId ; } if ( channel == DisplayChannel . Roughness ) { this . displayNodes . roughnessNode = nodeId ; } if ( channel == DisplayChannel . Height ) { this . displayNodes . heightNode = nodeId ; } this . updateDisplayNode ( node ) ; } exposeVariable ( node , prop , varDisplayName ) { const varName = Guid . newGuid ( ) ; const dvar = this . designer . addVariable ( varName , varDisplayName , this . evalDesignerVariableType ( prop ) ) ; dvar . property = prop . clone ( ) ; dvar . property . name = varName ; dvar . property . displayName = varDisplayName ; this . designer . mapNodePropertyToVariable ( varName , node , prop . name ) ; } evalDesignerVariableType ( prop ) { if ( prop . type == PropertyType . Float ) { return DesignerVariableType . Float ; } else if ( prop . type == PropertyType . Int ) { return DesignerVariableType . Int ; } else if ( prop . type == PropertyType . Bool ) { return DesignerVariableType . Bool ; } else if ( prop . type == PropertyType . Enum ) { return DesignerVariableType . Enum ; } else if ( prop . type == PropertyType . Color ) { return DesignerVariableType . Color ; } else { console . log ( "error: invalid property type for variable" , prop ) ; } return null ; } update ( ) { if ( this . designer ) this . designer . update ( ) ; } draw ( ) { if ( this . graph ) this . graph . draw ( ) ; } load ( data ) { this . clearTextureChannels ( ) ; let library ; if ( ! data [ "libraryVersion" ] ) { library = createV2Library ( ) ; } else { let libVer = data [ "libraryVersion" ] ; if ( libVer === "v0" ) library = createLibrary ( ) ; if ( libVer === "v1" ) library = createV2Library ( ) ; else if ( libVer === "v2" ) library = createV2Library ( ) ; else console . error ( `Invalid library version ${ libVer }, project might not load properly` ) ; } const d = Designer . load ( data , library ) ; const g = NodeScene . load ( d , data [ "scene" ] , this . canvas ) ; this . library = library ; this . setDesigner ( d ) ; this . setScene ( g ) ; if ( data [ "editor" ] != null ) { const e = data [ "editor" ] ; for ( const channelName in e . textureChannels ) { if ( ! e . textureChannels . hasOwnProperty ( channelName ) ) continue ; const node = this . graph . getNodeById ( e . textureChannels [ channelName ] ) ; if ( node ) this . assignNodeToTextureChannel ( node . id , channelName ) ; } } } save ( ) { const data = this . designer . save ( ) ; data [ "scene" ] = this . graph . save ( ) ; const textureChannels = { } ; for ( const channelName of this . textureChannels . keys ( ) ) { textureChannels [ channelName ] = this . textureChannels . get ( channelName ) . id ; } data [ "editor" ] = { textureChannels : textureChannels } ; data [ "libraryVersion" ] = this . library . getVersionName ( ) ; return data ; } }