123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- import { Canvas, CircleMenu, ButtonInput, ContextMenu, Loader } from '../libs/flow.module.js';
- import { StandardMaterialEditor } from './materials/StandardMaterialEditor.js';
- import { OperatorEditor } from './math/OperatorEditor.js';
- import { NormalizeEditor } from './math/NormalizeEditor.js';
- import { InvertEditor } from './math/InvertEditor.js';
- import { LimiterEditor } from './math/LimiterEditor.js';
- import { DotEditor } from './math/DotEditor.js';
- import { PowerEditor } from './math/PowerEditor.js';
- import { TrigonometryEditor } from './math/TrigonometryEditor.js';
- import { FloatEditor } from './inputs/FloatEditor.js';
- import { Vector2Editor } from './inputs/Vector2Editor.js';
- import { Vector3Editor } from './inputs/Vector3Editor.js';
- import { Vector4Editor } from './inputs/Vector4Editor.js';
- import { SliderEditor } from './inputs/SliderEditor.js';
- import { ColorEditor } from './inputs/ColorEditor.js';
- import { BlendEditor } from './display/BlendEditor.js';
- import { UVEditor } from './accessors/UVEditor.js';
- import { PositionEditor } from './accessors/PositionEditor.js';
- import { NormalEditor } from './accessors/NormalEditor.js';
- import { TimerEditor } from './utils/TimerEditor.js';
- import { OscillatorEditor } from './utils/OscillatorEditor.js';
- import { CheckerEditor } from './procedural/CheckerEditor.js';
- import { EventDispatcher } from 'three';
- export const ClassLib = {
- 'StandardMaterialEditor': StandardMaterialEditor,
- 'OperatorEditor': OperatorEditor,
- 'NormalizeEditor': NormalizeEditor,
- 'InvertEditor': InvertEditor,
- 'LimiterEditor': LimiterEditor,
- 'DotEditor': DotEditor,
- 'PowerEditor': PowerEditor,
- 'TrigonometryEditor': TrigonometryEditor,
- 'FloatEditor': FloatEditor,
- 'Vector2Editor': Vector2Editor,
- 'Vector3Editor': Vector3Editor,
- 'Vector4Editor': Vector4Editor,
- 'SliderEditor': SliderEditor,
- 'ColorEditor': ColorEditor,
- 'BlendEditor': BlendEditor,
- 'UVEditor': UVEditor,
- 'PositionEditor': PositionEditor,
- 'NormalEditor': NormalEditor,
- 'TimerEditor': TimerEditor,
- 'OscillatorEditor': OscillatorEditor,
- 'CheckerEditor': CheckerEditor
- };
- export class NodeEditor extends EventDispatcher {
- constructor() {
- super();
- const domElement = document.createElement( 'flow' );
- const canvas = new Canvas();
- domElement.appendChild( canvas.dom );
- this.canvas = canvas;
- this.domElement = domElement;
- this.nodesContext = null;
- this.examplesContext = null;
- this._initMenu();
- this._initNodesContext();
- this._initExamplesContext();
- }
- add( node ) {
- this.canvas.add( node );
- return this;
- }
- get nodes() {
- return this.canvas.nodes;
- }
- newProject() {
- this.canvas.clear();
- this.dispatchEvent( { type: 'new' } );
- }
- loadJSON( json ) {
- this.canvas.clear();
- this.canvas.deserialize( json );
- this.dispatchEvent( { type: 'load' } );
- }
- _initMenu() {
- const menu = new CircleMenu();
- const menuButton = new ButtonInput().setIcon( 'ti ti-menu-2' );
- const examplesButton = new ButtonInput().setIcon( 'ti ti-file-symlink' ).setToolTip( 'Examples' );
- const newButton = new ButtonInput().setIcon( 'ti ti-file' ).setToolTip( 'New' );
- const openButton = new ButtonInput().setIcon( 'ti ti-upload' ).setToolTip( 'Open' );
- const saveButton = new ButtonInput().setIcon( 'ti ti-download' ).setToolTip( 'Save' );
- const hideContext = () => {
- this.examplesContext.hide();
- this.nodesContext.hide();
- };
- menuButton.onClick( () => {
- this.nodesContext.show( 60, 50 );
- } );
- examplesButton.onClick( () => {
- this.examplesContext.show( 60, 175 );
- } );
- newButton.onClick( () => {
- hideContext();
- this.newProject();
- } );
- openButton.onClick( () => {
- hideContext();
- const input = document.createElement( 'input' );
- input.type = 'file';
- input.onchange = e => {
- const file = e.target.files[ 0 ];
- const reader = new FileReader();
- reader.readAsText( file, 'UTF-8' );
- reader.onload = readerEvent => {
- const loader = new Loader( Loader.OBJECTS );
- const json = loader.parse( JSON.parse( readerEvent.target.result ), ClassLib );
- this.loadJSON( json );
- };
- };
- input.click();
- } );
- saveButton.onClick( () => {
- hideContext();
- const json = JSON.stringify( this.canvas.toJSON() );
- const a = document.createElement( 'a' );
- const file = new Blob( [ json ], { type: 'text/plain' } );
- a.href = URL.createObjectURL( file );
- a.download = 'node_editor.json';
- a.click();
- } );
- menu.add( menuButton )
- .add( newButton )
- .add( examplesButton )
- .add( openButton )
- .add( saveButton );
- this.domElement.appendChild( menu.dom );
- this.menu = menu;
- }
- _initExamplesContext() {
- const context = new ContextMenu();
- //**************//
- // MAIN
- //**************//
- const onClickExample = async ( button ) => {
- this.examplesContext.hide();
- const filename = button.getExtra();
- const loader = new Loader( Loader.OBJECTS );
- const json = await loader.load( `./jsm/node-editor/examples/${filename}.json`, ClassLib );
- this.loadJSON( json );
- };
- const addExample = ( context, name, filename = null ) => {
- filename = filename || name.replaceAll( ' ', '-' ).toLowerCase();
- context.add( new ButtonInput( name )
- .setIcon( 'ti ti-file-symlink' )
- .onClick( onClickExample )
- .setExtra( filename )
- );
- };
- //**************//
- // EXAMPLES
- //**************//
- const basicContext = new ContextMenu();
- const advancedContext = new ContextMenu();
- addExample( basicContext, 'Animate UV' );
- addExample( basicContext, 'Fake top light' );
- addExample( basicContext, 'Oscillator color' );
- addExample( advancedContext, 'Rim' );
- //**************//
- // MAIN
- //**************//
- context.add( new ButtonInput( 'Basic' ), basicContext );
- context.add( new ButtonInput( 'Advanced' ), advancedContext );
- this.domElement.appendChild( context.dom );
- this.examplesContext = context;
- }
- _initNodesContext() {
- const context = new ContextMenu( this.domElement );
- let isContext = false;
- let contextPosition = {};
- const add = ( node ) => {
- const canvas = this.canvas;
- const canvasRect = canvas.rect;
- if ( isContext ) {
- node.setPosition(
- contextPosition.x,
- contextPosition.y
- );
- } else {
- const defaultOffsetX = 350 / 2;
- const defaultOffsetY = 20;
- node.setPosition(
- ( canvas.relativeX + ( canvasRect.width / 2 ) ) - defaultOffsetX,
- ( canvas.relativeY + ( canvasRect.height / 2 ) ) - defaultOffsetY
- );
- }
- context.hide();
- this.add( node );
- this.canvas.select( node );
- isContext = false;
- };
- context.onContext( () => {
- isContext = true;
- const { relativeClientX, relativeClientY } = this.canvas;
- contextPosition.x = relativeClientX;
- contextPosition.y = relativeClientY;
- } );
- //**************//
- // INPUTS
- //**************//
- const inputsContext = new ContextMenu();
- const sliderInput = new ButtonInput( 'Slider' ).setIcon( 'ti ti-adjustments-horizontal' )
- .onClick( () => add( new SliderEditor() ) );
- const floatInput = new ButtonInput( 'Float' ).setIcon( 'ti ti-box-multiple-1' )
- .onClick( () => add( new FloatEditor() ) );
- const vector2Input = new ButtonInput( 'Vector 2' ).setIcon( 'ti ti-box-multiple-2' )
- .onClick( () => add( new Vector2Editor() ) );
- const vector3Input = new ButtonInput( 'Vector 3' ).setIcon( 'ti ti-box-multiple-3' )
- .onClick( () => add( new Vector3Editor() ) );
- const vector4Input = new ButtonInput( 'Vector 4' ).setIcon( 'ti ti-box-multiple-4' )
- .onClick( () => add( new Vector4Editor() ) );
- const colorInput = new ButtonInput( 'Color' ).setIcon( 'ti ti-palette' )
- .onClick( () => add( new ColorEditor() ) );
- //const mapInput = new ButtonInput( 'Map' ).setIcon( 'ti ti-photo' );
- //const cubeMapInput = new ButtonInput( 'Cube Map' ).setIcon( 'ti ti-box' );
- //const integerInput = new ButtonInput( 'Integer' ).setIcon( 'ti ti-list-numbers' );
- inputsContext
- .add( sliderInput )
- .add( floatInput )
- .add( vector2Input )
- .add( vector3Input )
- .add( vector4Input )
- .add( colorInput );
- //**************//
- // MATH
- //**************//
- const mathContext = new ContextMenu();
- const operatorsNode = new ButtonInput( 'Operator' ).setIcon( 'ti ti-math-symbols' )
- .onClick( () => add( new OperatorEditor() ) );
- const normalizeNode = new ButtonInput( 'Normalize' ).setIcon( 'ti ti-fold' )
- .onClick( () => add( new NormalizeEditor() ) );
- const invertNode = new ButtonInput( 'Invert' ).setToolTip( 'Negate' ).setIcon( 'ti ti-flip-vertical' )
- .onClick( () => add( new InvertEditor() ) );
- const limiterNode = new ButtonInput( 'Limiter' ).setToolTip( 'Min / Max' ).setIcon( 'ti ti-arrow-bar-to-up' )
- .onClick( () => add( new LimiterEditor() ) );
- const dotNode = new ButtonInput( 'Dot Product' ).setIcon( 'ti ti-arrows-up-left' )
- .onClick( () => add( new DotEditor() ) );
- const powNode = new ButtonInput( 'Power' ).setIcon( 'ti ti-arrow-up-right' )
- .onClick( () => add( new PowerEditor() ) );
- const triNode = new ButtonInput( 'Trigonometry' ).setToolTip( 'Sin / Cos / Tan' ).setIcon( 'ti ti-wave-sine' )
- .onClick( () => add( new TrigonometryEditor() ) );
- mathContext
- .add( operatorsNode )
- .add( invertNode )
- .add( limiterNode )
- .add( dotNode )
- .add( powNode )
- .add( triNode )
- .add( normalizeNode );
- //**************//
- // ACCESSORS
- //**************//
- const accessorsContext = new ContextMenu();
- const uvNode = new ButtonInput( 'UV' ).setIcon( 'ti ti-details' )
- .onClick( () => add( new UVEditor() ) );
- const positionNode = new ButtonInput( 'Position' ).setIcon( 'ti ti-hierarchy' )
- .onClick( () => add( new PositionEditor() ) );
- const normalNode = new ButtonInput( 'Normal' ).setIcon( 'ti ti-fold-up' )
- .onClick( () => add( new NormalEditor() ) );
- accessorsContext
- .add( uvNode )
- .add( positionNode )
- .add( normalNode );
- //**************//
- // PROCEDURAL
- //**************//
- const proceduralContext = new ContextMenu();
- const checkerNode = new ButtonInput( 'Checker' ).setIcon( 'ti ti-border-outer' )
- .onClick( () => add( new CheckerEditor() ) );
- proceduralContext
- .add( checkerNode );
- //**************//
- // DISPLAY
- //**************//
- const displayContext = new ContextMenu();
- const blendNode = new ButtonInput( 'Blend' ).setIcon( 'ti ti-layers-subtract' )
- .onClick( () => add( new BlendEditor() ) );
- displayContext
- .add( blendNode );
- //**************//
- // UTILS
- //**************//
- const utilsContext = new ContextMenu();
- const timerNode = new ButtonInput( 'Timer' ).setIcon( 'ti ti-clock' )
- .onClick( () => add( new TimerEditor() ) );
- const oscNode = new ButtonInput( 'Oscillator' ).setIcon( 'ti ti-wave-sine' )
- .onClick( () => add( new OscillatorEditor() ) );
- utilsContext
- .add( timerNode )
- .add( oscNode );
- //**************//
- // MAIN
- //**************//
- context.add( new ButtonInput( 'Inputs' ).setIcon( 'ti ti-forms' ), inputsContext );
- context.add( new ButtonInput( 'Accessors' ).setIcon( 'ti ti-vector-triangle' ), accessorsContext );
- context.add( new ButtonInput( 'Display' ).setIcon( 'ti ti-brightness' ), displayContext );
- context.add( new ButtonInput( 'Math' ).setIcon( 'ti ti-calculator' ), mathContext );
- context.add( new ButtonInput( 'Procedural' ).setIcon( 'ti ti-infinity' ), proceduralContext );
- context.add( new ButtonInput( 'Utils' ).setIcon( 'ti ti-apps' ), utilsContext );
- this.nodesContext = context;
- }
- }
|