锦水大屏
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

455 lines
11 KiB

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;
}
}