Archived
Private
Public Access
1
0

Initial commit

This commit is contained in:
2022-09-04 12:45:01 +02:00
commit f4a01d6a69
11601 changed files with 4206660 additions and 0 deletions

View File

@@ -0,0 +1,763 @@
import { Styles, Canvas, CircleMenu, ButtonInput, ContextMenu, Tips, Search, Loader } from '../libs/flow.module.js';
import { BasicMaterialEditor } from './materials/BasicMaterialEditor.js';
import { StandardMaterialEditor } from './materials/StandardMaterialEditor.js';
import { PointsMaterialEditor } from './materials/PointsMaterialEditor.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 { AngleEditor } from './math/AngleEditor.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 { TextureEditor } from './inputs/TextureEditor.js';
import { BlendEditor } from './display/BlendEditor.js';
import { NormalMapEditor } from './display/NormalMapEditor.js';
import { UVEditor } from './accessors/UVEditor.js';
import { PositionEditor } from './accessors/PositionEditor.js';
import { NormalEditor } from './accessors/NormalEditor.js';
import { PreviewEditor } from './utils/PreviewEditor.js';
import { TimerEditor } from './utils/TimerEditor.js';
import { OscillatorEditor } from './utils/OscillatorEditor.js';
import { SplitEditor } from './utils/SplitEditor.js';
import { JoinEditor } from './utils/JoinEditor.js';
import { CheckerEditor } from './procedural/CheckerEditor.js';
import { PointsEditor } from './scene/PointsEditor.js';
import { MeshEditor } from './scene/MeshEditor.js';
import { FileEditor } from './core/FileEditor.js';
import { EventDispatcher } from 'three';
Styles.icons.unlink = 'ti ti-unlink';
export const NodeList = [
{
name: 'Inputs',
icon: 'forms',
children: [
{
name: 'Slider',
icon: 'adjustments-horizontal',
nodeClass: SliderEditor
},
{
name: 'Float',
icon: 'box-multiple-1',
nodeClass: FloatEditor
},
{
name: 'Vector 2',
icon: 'box-multiple-2',
nodeClass: Vector2Editor
},
{
name: 'Vector 3',
icon: 'box-multiple-3',
nodeClass: Vector3Editor
},
{
name: 'Vector 4',
icon: 'box-multiple-4',
nodeClass: Vector4Editor
},
{
name: 'Color',
icon: 'palette',
nodeClass: ColorEditor
},
{
name: 'Texture',
icon: 'photo',
nodeClass: TextureEditor
}
]
},
{
name: 'Accessors',
icon: 'vector-triangle',
children: [
{
name: 'UV',
icon: 'details',
nodeClass: UVEditor
},
{
name: 'Position',
icon: 'hierarchy',
nodeClass: PositionEditor
},
{
name: 'Normal',
icon: 'fold-up',
nodeClass: NormalEditor
}
]
},
{
name: 'Display',
icon: 'brightness',
children: [
{
name: 'Blend',
icon: 'layers-subtract',
nodeClass: BlendEditor
},
{
name: 'Normal Map',
icon: 'chart-line',
nodeClass: NormalMapEditor
}
]
},
{
name: 'Math',
icon: 'calculator',
children: [
{
name: 'Operator',
icon: 'math-symbols',
nodeClass: OperatorEditor
},
{
name: 'Invert',
icon: 'flip-vertical',
tip: 'Negate',
nodeClass: InvertEditor
},
{
name: 'Limiter',
icon: 'arrow-bar-to-up',
tip: 'Min / Max',
nodeClass: LimiterEditor
},
{
name: 'Dot Product',
icon: 'arrows-up-left',
nodeClass: DotEditor
},
{
name: 'Power',
icon: 'arrow-up-right',
nodeClass: PowerEditor
},
{
name: 'Trigonometry',
icon: 'wave-sine',
tip: 'Sin / Cos / Tan / ...',
nodeClass: TrigonometryEditor
},
{
name: 'Angle',
icon: 'angle',
tip: 'Degress / Radians',
nodeClass: AngleEditor
},
{
name: 'Normalize',
icon: 'fold',
nodeClass: NormalizeEditor
}
]
},
{
name: 'Procedural',
icon: 'infinity',
children: [
{
name: 'Checker',
icon: 'border-outer',
nodeClass: CheckerEditor
}
]
},
{
name: 'Utils',
icon: 'apps',
children: [
{
name: 'Preview',
icon: 'square-check',
nodeClass: PreviewEditor
},
{
name: 'Timer',
icon: 'clock',
nodeClass: TimerEditor
},
{
name: 'Oscillator',
icon: 'wave-sine',
nodeClass: OscillatorEditor
},
{
name: 'Split',
icon: 'arrows-split-2',
nodeClass: SplitEditor
},
{
name: 'Join',
icon: 'arrows-join-2',
nodeClass: JoinEditor
}
]
},
/*{
name: 'Scene',
icon: '3d-cube-sphere',
children: [
{
name: 'Mesh',
icon: '3d-cube-sphere',
nodeClass: MeshEditor
}
]
},*/
{
name: 'Material',
icon: 'circles',
children: [
{
name: 'Basic Material',
icon: 'circle',
nodeClass: BasicMaterialEditor
},
{
name: 'Standard Material',
icon: 'circle',
nodeClass: StandardMaterialEditor
},
{
name: 'Points Material',
icon: 'circle-dotted',
nodeClass: PointsMaterialEditor
}
]
}
];
export const ClassLib = {
BasicMaterialEditor,
StandardMaterialEditor,
PointsMaterialEditor,
PointsEditor,
MeshEditor,
OperatorEditor,
NormalizeEditor,
InvertEditor,
LimiterEditor,
DotEditor,
PowerEditor,
AngleEditor,
TrigonometryEditor,
FloatEditor,
Vector2Editor,
Vector3Editor,
Vector4Editor,
SliderEditor,
ColorEditor,
TextureEditor,
BlendEditor,
NormalMapEditor,
UVEditor,
PositionEditor,
NormalEditor,
TimerEditor,
OscillatorEditor,
SplitEditor,
JoinEditor,
CheckerEditor
};
export class NodeEditor extends EventDispatcher {
constructor( scene = null ) {
super();
const domElement = document.createElement( 'flow' );
const canvas = new Canvas();
domElement.append( canvas.dom );
this.scene = scene;
this.canvas = canvas;
this.domElement = domElement;
this.nodesContext = null;
this.examplesContext = null;
this._initUpload();
this._initTips();
this._initMenu();
this._initSearch();
this._initNodesContext();
this._initExamplesContext();
}
centralizeNode( node ) {
const canvas = this.canvas;
const canvasRect = canvas.rect;
const nodeRect = node.dom.getBoundingClientRect();
const defaultOffsetX = nodeRect.width;
const defaultOffsetY = nodeRect.height;
node.setPosition(
( canvas.relativeX + ( canvasRect.width / 2 ) ) - defaultOffsetX,
( canvas.relativeY + ( canvasRect.height / 2 ) ) - defaultOffsetY
);
}
add( node ) {
const onRemove = () => {
node.removeEventListener( 'remove', onRemove );
node.setEditor( null );
};
node.setEditor( this );
node.addEventListener( 'remove', onRemove );
this.canvas.add( node );
this.dispatchEvent( { type: 'add', node } );
return this;
}
get nodes() {
return this.canvas.nodes;
}
newProject() {
this.canvas.clear();
this.dispatchEvent( { type: 'new' } );
}
loadJSON( json ) {
const canvas = this.canvas;
canvas.clear();
canvas.deserialize( json );
for ( const node of canvas.nodes ) {
this.add( node );
}
this.dispatchEvent( { type: 'load' } );
}
_initUpload() {
const canvas = this.canvas;
canvas.onDrop( () => {
for ( const item of canvas.droppedItems ) {
if ( /^image\//.test( item.type ) === true ) {
const { relativeClientX, relativeClientY } = canvas;
const file = item.getAsFile();
const fileEditor = new FileEditor( file );
fileEditor.setPosition(
relativeClientX - ( fileEditor.getWidth() / 2 ),
relativeClientY - 20
);
this.add( fileEditor );
}
}
} );
}
_initTips() {
this.tips = new Tips();
this.domElement.append( this.tips.dom );
}
_initMenu() {
const menu = new CircleMenu();
const menuButton = new ButtonInput().setIcon( 'ti ti-apps' ).setToolTip( 'Add' );
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' );
menuButton.onClick( () => this.nodesContext.open() );
examplesButton.onClick( () => this.examplesContext.open() );
newButton.onClick( () => {
if ( confirm( 'Are you sure?' ) === true ) {
this.newProject();
}
} );
openButton.onClick( () => {
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( () => {
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( examplesButton )
.add( menuButton )
.add( newButton )
.add( openButton )
.add( saveButton );
this.domElement.append( 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.examplesContext = context;
}
_initSearch() {
const traverseNodeEditors = ( item ) => {
if ( item.nodeClass ) {
const button = new ButtonInput( item.name );
button.setIcon( `ti ti-${item.icon}` );
button.addEventListener( 'complete', () => {
const node = new item.nodeClass();
this.add( node );
this.centralizeNode( node );
} );
search.add( button );
}
if ( item.children ) {
for ( const subItem of item.children ) {
traverseNodeEditors( subItem );
}
}
};
const search = new Search();
search.forceAutoComplete = true;
search.onFilter( () => {
search.clear();
for ( const item of NodeList ) {
traverseNodeEditors( item );
}
const object3d = this.scene;
if ( object3d !== null ) {
object3d.traverse( ( obj3d ) => {
if ( obj3d.isMesh === true || obj3d.isPoints === true ) {
let prefix = null;
let icon = null;
let editorClass = null;
if ( obj3d.isMesh === true ) {
prefix = 'Mesh';
icon = 'ti ti-3d-cube-sphere';
editorClass = MeshEditor;
} else if ( obj3d.isPoints === true ) {
prefix = 'Points';
icon = 'ti ti-border-none';
editorClass = PointsEditor;
}
const button = new ButtonInput( `${prefix} - ${obj3d.name}` );
button.setIcon( icon );
button.addEventListener( 'complete', () => {
for ( const node of this.canvas.nodes ) {
if ( node.value === obj3d ) {
// prevent duplicated node
this.canvas.select( node );
return;
}
}
const node = new editorClass( obj3d );
this.add( node );
this.centralizeNode( node );
} );
search.add( button );
}
} );
}
} );
search.onSubmit( () => {
if ( search.currentFiltered !== null ) {
search.currentFiltered.button.dispatchEvent( new Event( 'complete' ) );
}
} );
this.domElement.append( search.dom );
}
_initNodesContext() {
const context = new ContextMenu( this.domElement );
let isContext = false;
const contextPosition = {};
const add = ( node ) => {
if ( isContext ) {
node.setPosition(
Math.round( contextPosition.x ),
Math.round( contextPosition.y )
);
} else {
this.centralizeNode( node );
}
context.hide();
this.add( node );
this.canvas.select( node );
isContext = false;
};
context.onContext( () => {
isContext = true;
const { relativeClientX, relativeClientY } = this.canvas;
contextPosition.x = Math.round( relativeClientX );
contextPosition.y = Math.round( relativeClientY );
} );
//**************//
// INPUTS
//**************//
const createButtonMenu = ( item ) => {
const button = new ButtonInput( item.name );
button.setIcon( `ti ti-${item.icon}` );
let context = null;
if ( item.nodeClass ) {
button.onClick( () => add( new item.nodeClass() ) );
}
if ( item.tip ) {
button.setToolTip( item.tip );
}
if ( item.children ) {
context = new ContextMenu();
for ( const subItem of item.children ) {
const buttonMenu = createButtonMenu( subItem );
context.add( buttonMenu.button, buttonMenu.context );
}
}
return { button, context };
};
for ( const item of NodeList ) {
const buttonMenu = createButtonMenu( item );
context.add( buttonMenu.button, buttonMenu.context );
}
this.nodesContext = context;
}
}

View File

@@ -0,0 +1,30 @@
import { SelectInput, Element } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { NormalNode } from 'three-nodes/Nodes.js';
export class NormalEditor extends BaseNode {
constructor() {
const node = new NormalNode();
super( 'Normal', 3, node, 200 );
const optionsField = new SelectInput( [
{ name: 'Local', value: NormalNode.LOCAL },
{ name: 'World', value: NormalNode.WORLD },
{ name: 'View', value: NormalNode.VIEW },
{ name: 'Geometry', value: NormalNode.GEOMETRY }
], NormalNode.LOCAL ).onChange( () => {
node.scope = optionsField.getValue();
this.invalidate();
} );
this.add( new Element().add( optionsField ) );
}
}

View File

@@ -0,0 +1,30 @@
import { SelectInput, Element } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { PositionNode } from 'three-nodes/Nodes.js';
export class PositionEditor extends BaseNode {
constructor() {
const node = new PositionNode();
super( 'Position', 3, node, 200 );
const optionsField = new SelectInput( [
{ name: 'Local', value: PositionNode.LOCAL },
{ name: 'World', value: PositionNode.WORLD },
{ name: 'View', value: PositionNode.VIEW },
{ name: 'View Direction', value: PositionNode.VIEW_DIRECTION }
], PositionNode.LOCAL ).onChange( () => {
node.scope = optionsField.getValue();
this.invalidate();
} );
this.add( new Element().add( optionsField ) );
}
}

View File

@@ -0,0 +1,25 @@
import { SelectInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { UVNode } from 'three-nodes/Nodes.js';
export class UVEditor extends BaseNode {
constructor() {
const node = new UVNode();
super( 'UV', 2, node, 200 );
const optionsField = new SelectInput( [ '1', '2' ], 0 ).onChange( () => {
node.index = Number( optionsField.getValue() );
this.invalidate();
} );
this.add( new LabelElement( 'Channel' ).add( optionsField ) );
}
}

View File

@@ -0,0 +1,89 @@
import { ObjectNode } from '../../libs/flow.module.js';
export const onNodeValidElement = ( inputElement, outputElement ) => {
const outputObject = outputElement.getObject();
if ( ! outputObject || ! outputObject.isNode ) {
return false;
}
};
export class BaseNode extends ObjectNode {
constructor( name, inputLength, value = null, width = 300 ) {
const getObjectCallback = ( /*output = null*/ ) => {
return this.value;
};
super( name, inputLength, getObjectCallback, width );
this.setOutputColor( this.getColorValueFromValue( value ) );
this.editor = null;
this.value = value;
this.onValidElement = onNodeValidElement;
}
serialize( data ) {
super.serialize( data );
delete data.width;
}
deserialize( data ) {
delete data.width;
super.deserialize( data );
}
setEditor( value ) {
this.editor = value;
return this;
}
getColorValueFromValue( value ) {
if ( ! value ) return;
if ( value.isMaterial === true ) {
return 'forestgreen';
} else if ( value.isObject3D === true ) {
return 'orange';
} else if ( value instanceof File ) {
return 'aqua';
}
}
add( element ) {
element.onValid( ( source, target ) => this.onValidElement( source, target ) );
return super.add( element );
}
}

View File

@@ -0,0 +1,17 @@
import { StringInput, Element } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
export class FileEditor extends BaseNode {
constructor( file ) {
super( 'File', 1, file, 250 );
this.file = file;
this.nameInput = new StringInput( file.name ).setReadOnly( true );
this.add( new Element().add( this.nameInput ) );
}
}

View File

@@ -0,0 +1,44 @@
import { LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MathNode, FloatNode } from 'three-nodes/Nodes.js';
const NULL_VALUE = new FloatNode();
const ONE_VALUE = new FloatNode( 1 );
export class BlendEditor extends BaseNode {
constructor() {
const node = new MathNode( MathNode.MIX, NULL_VALUE, NULL_VALUE, ONE_VALUE );
super( 'Blend', 3, node, 200 );
const aElement = new LabelElement( 'Base' ).setInput( 3 );
const bElement = new LabelElement( 'Blend' ).setInput( 3 );
const cElement = new LabelElement( 'Opacity' ).setInput( 1 );
aElement.onConnect( () => {
node.aNode = aElement.getLinkedObject() || NULL_VALUE;
} );
bElement.onConnect( () => {
node.bNode = bElement.getLinkedObject() || NULL_VALUE;
} );
cElement.onConnect( () => {
node.cNode = cElement.getLinkedObject() || ONE_VALUE;
} );
this.add( aElement )
.add( bElement )
.add( cElement );
}
}

View File

@@ -0,0 +1,49 @@
import { SelectInput, Element, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { NormalMapNode, FloatNode } from 'three-nodes/Nodes.js';
import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three';
const nullValue = new FloatNode( 0 ).setConst( true );
export class NormalMapEditor extends BaseNode {
constructor() {
const node = new NormalMapNode( nullValue );
super( 'Normal Map', 3, node, 175 );
const source = new LabelElement( 'Source' ).setInput( 3 ).onConnect( () => {
node.node = source.getLinkedObject() || nullValue;
this.invalidate();
} );
const scale = new LabelElement( 'Scale' ).setInput( 3 ).onConnect( () => {
node.scaleNode = scale.getLinkedObject();
this.invalidate();
} );
const optionsField = new SelectInput( [
{ name: 'Tangent Space', value: TangentSpaceNormalMap },
{ name: 'Object Space', value: ObjectSpaceNormalMap }
], TangentSpaceNormalMap ).onChange( () => {
node.normalMapType = Number( optionsField.getValue() );
this.invalidate();
} );
this.add( new Element().add( optionsField ) )
.add( source )
.add( scale );
}
}

View File

@@ -0,0 +1 @@
{"objects":{"733":{"x":1718,"y":136,"width":300,"elements":[734,736,737,738,739],"id":733,"type":"StandardMaterialEditor"},"734":{"outputLength":1,"style":"blue","title":"Standard Material","id":734,"type":"TitleElement"},"736":{"inputLength":3,"inputs":[740],"links":[809],"label":"Color","id":736,"type":"LabelElement"},"737":{"inputLength":1,"inputs":[741],"label":"Opacity","icon":"ti ti-layers-subtract","id":737,"type":"LabelElement"},"738":{"inputLength":1,"inputs":[743],"label":"Metalness","id":738,"type":"LabelElement"},"739":{"inputLength":1,"inputs":[745],"label":"Roughness","id":739,"type":"LabelElement"},"740":{"value":16777215,"id":740,"type":"ColorInput"},"741":{"min":0,"max":1,"value":1,"id":741,"type":"SliderInput"},"743":{"min":0,"max":1,"value":0,"id":743,"type":"SliderInput"},"745":{"min":0,"max":1,"value":1,"id":745,"type":"SliderInput"},"752":{"x":155,"y":230,"width":250,"elements":[753,759,760,757],"id":752,"type":"TimerEditor"},"753":{"outputLength":1,"title":"Timer","icon":"ti ti-clock","id":753,"type":"TitleElement"},"755":{"value":0.218,"id":755,"type":"NumberInput"},"756":{"value":0.04,"id":756,"type":"NumberInput"},"757":{"inputs":[758],"id":757,"type":"Element"},"758":{"value":"Reset","id":758,"type":"ButtonInput"},"759":{"inputs":[755],"id":759,"type":"Element"},"760":{"inputs":[756],"label":"Scale","id":760,"type":"LabelElement"},"768":{"x":202,"y":79,"width":250,"elements":[769,772],"id":768,"type":"UVEditor"},"769":{"outputLength":2,"style":"red","title":"UV","id":769,"type":"TitleElement"},"771":{"options":["1","2"],"value":"0","id":771,"type":"SelectInput"},"772":{"inputs":[771],"label":"Channel","id":772,"type":"LabelElement"},"776":{"x":612,"y":102,"width":250,"elements":[777,782,780,781],"id":776,"type":"OperatorEditor"},"777":{"outputLength":1,"title":"Operator","id":777,"type":"TitleElement"},"779":{"options":[{"name":"+ Addition","value":"+"},{"name":"- Subtraction","value":"-"},{"name":"* Multiplication","value":"*"},{"name":"/ Division","value":"/"}],"value":"+","id":779,"type":"SelectInput"},"780":{"inputLength":3,"links":[769],"label":"A","id":780,"type":"LabelElement"},"781":{"inputLength":3,"links":[753],"label":"B","id":781,"type":"LabelElement"},"782":{"inputs":[779],"id":782,"type":"Element"},"788":{"x":1047,"y":158,"width":250,"elements":[789,794,792,793],"id":788,"type":"OperatorEditor"},"789":{"outputLength":1,"title":"Operator","id":789,"type":"TitleElement"},"791":{"options":[{"name":"+ Addition","value":"+"},{"name":"- Subtraction","value":"-"},{"name":"* Multiplication","value":"*"},{"name":"/ Division","value":"/"}],"value":"*","id":791,"type":"SelectInput"},"792":{"inputLength":3,"links":[777],"label":"A","id":792,"type":"LabelElement"},"793":{"inputLength":3,"links":[801],"label":"B","id":793,"type":"LabelElement"},"794":{"inputs":[791],"id":794,"type":"Element"},"800":{"x":601,"y":345,"width":250,"elements":[801,804],"id":800,"type":"FloatEditor"},"801":{"outputLength":1,"title":"Float","icon":"ti ti-box-multiple-1","id":801,"type":"TitleElement"},"803":{"value":24.12,"id":803,"type":"NumberInput"},"804":{"inputs":[803],"id":804,"type":"Element"},"808":{"x":1402,"y":14,"width":200,"elements":[809,811],"id":808,"type":"CheckerEditor"},"809":{"outputLength":1,"title":"Checker","id":809,"type":"TitleElement"},"811":{"inputLength":2,"links":[789],"label":"UV","id":811,"type":"LabelElement"},"837":{"x":2160,"y":128,"width":300,"elements":[838,841,851,852,853,854],"id":837,"type":"MeshEditor"},"838":{"outputLength":1,"title":"Mesh","id":838,"type":"TitleElement"},"840":{"value":"Stanford_Bunny","id":840,"type":"StringInput"},"841":{"inputs":[840],"label":"Name","id":841,"type":"LabelElement"},"842":{"value":0,"id":842,"type":"NumberInput"},"843":{"value":0,"id":843,"type":"NumberInput"},"844":{"value":10,"id":844,"type":"NumberInput"},"845":{"value":0,"id":845,"type":"NumberInput"},"846":{"value":0,"id":846,"type":"NumberInput"},"847":{"value":0,"id":847,"type":"NumberInput"},"848":{"value":100,"id":848,"type":"NumberInput"},"849":{"value":100,"id":849,"type":"NumberInput"},"850":{"value":100,"id":850,"type":"NumberInput"},"851":{"inputs":[842,843,844],"label":"Position","id":851,"type":"LabelElement"},"852":{"inputs":[845,846,847],"label":"Rotation","id":852,"type":"LabelElement"},"853":{"inputs":[848,849,850],"label":"Scale","id":853,"type":"LabelElement"},"854":{"inputLength":1,"links":[734],"label":"Material","id":854,"type":"LabelElement"}},"nodes":[733,752,768,776,788,800,808,837],"id":2,"type":"Canvas"}

View File

@@ -0,0 +1 @@
{"objects":{"389":{"x":885,"y":119,"width":300,"elements":[390,392,393,394,395],"id":389,"type":"StandardMaterialEditor"},"390":{"outputLength":1,"style":"blue","title":"Standard Material","id":390,"type":"TitleElement"},"392":{"inputLength":3,"inputs":[396],"links":[421],"label":"Color","id":392,"type":"LabelElement"},"393":{"inputLength":1,"inputs":[397],"label":"Opacity","icon":"ti ti-layers-subtract","id":393,"type":"LabelElement"},"394":{"inputLength":1,"inputs":[399],"label":"Metalness","id":394,"type":"LabelElement"},"395":{"inputLength":1,"inputs":[401],"label":"Roughness","id":395,"type":"LabelElement"},"396":{"value":16777215,"id":396,"type":"ColorInput"},"397":{"min":0,"max":1,"value":1,"id":397,"type":"SliderInput"},"399":{"min":0,"max":1,"value":0,"id":399,"type":"SliderInput"},"401":{"min":0,"max":1,"value":1,"id":401,"type":"SliderInput"},"408":{"x":73,"y":296,"width":325,"elements":[409,414],"id":408,"type":"Vector3Editor"},"409":{"outputLength":3,"title":"Vector 3","icon":"ti ti-box-multiple-3","id":409,"type":"TitleElement"},"411":{"value":0,"id":411,"type":"NumberInput"},"412":{"value":1,"id":412,"type":"NumberInput"},"413":{"value":0,"id":413,"type":"NumberInput"},"414":{"inputs":[411,412,413],"label":"Values","id":414,"type":"LabelElement"},"420":{"x":541,"y":199,"width":200,"elements":[421,423,424],"id":420,"type":"DotEditor"},"421":{"outputLength":1,"title":"Dot Product","id":421,"type":"TitleElement"},"423":{"inputLength":3,"links":[429],"label":"A","id":423,"type":"LabelElement"},"424":{"inputLength":3,"links":[409],"label":"B","id":424,"type":"LabelElement"},"428":{"x":106,"y":158,"width":250,"elements":[429,432],"id":428,"type":"NormalEditor"},"429":{"outputLength":3,"style":"red","title":"Normal","id":429,"type":"TitleElement"},"431":{"options":[{"name":"Local","value":"local"},{"name":"World","value":"world"},{"name":"View","value":"view"}],"value":"world","id":431,"type":"SelectInput"},"432":{"inputs":[431],"id":432,"type":"Element"},"473":{"x":1314,"y":113,"width":300,"elements":[474,477,487,488,489,490],"id":473,"type":"MeshEditor"},"474":{"outputLength":1,"title":"Mesh","id":474,"type":"TitleElement"},"476":{"value":"Stanford_Bunny","id":476,"type":"StringInput"},"477":{"inputs":[476],"label":"Name","id":477,"type":"LabelElement"},"478":{"value":0,"id":478,"type":"NumberInput"},"479":{"value":0,"id":479,"type":"NumberInput"},"480":{"value":10,"id":480,"type":"NumberInput"},"481":{"value":0,"id":481,"type":"NumberInput"},"482":{"value":0,"id":482,"type":"NumberInput"},"483":{"value":0,"id":483,"type":"NumberInput"},"484":{"value":100,"id":484,"type":"NumberInput"},"485":{"value":100,"id":485,"type":"NumberInput"},"486":{"value":100,"id":486,"type":"NumberInput"},"487":{"inputs":[478,479,480],"label":"Position","id":487,"type":"LabelElement"},"488":{"inputs":[481,482,483],"label":"Rotation","id":488,"type":"LabelElement"},"489":{"inputs":[484,485,486],"label":"Scale","id":489,"type":"LabelElement"},"490":{"inputLength":1,"links":[390],"label":"Material","id":490,"type":"LabelElement"}},"nodes":[389,408,420,428,473],"id":2,"type":"Canvas"}

View File

@@ -0,0 +1 @@
{"objects":{"247":{"x":1602,"y":-3,"width":300,"elements":[248,250,251,252,253],"id":247,"type":"StandardMaterialEditor"},"248":{"outputLength":1,"style":"blue","title":"Standard Material","id":248,"type":"TitleElement"},"250":{"inputLength":3,"inputs":[254],"links":[293],"label":"Color","id":250,"type":"LabelElement"},"251":{"inputLength":1,"inputs":[255],"label":"Opacity","icon":"ti ti-layers-subtract","id":251,"type":"LabelElement"},"252":{"inputLength":1,"inputs":[257],"label":"Metalness","id":252,"type":"LabelElement"},"253":{"inputLength":1,"inputs":[259],"label":"Roughness","id":253,"type":"LabelElement"},"254":{"value":16777215,"id":254,"type":"ColorInput"},"255":{"min":0,"max":1,"value":1,"id":255,"type":"SliderInput"},"257":{"min":0,"max":1,"value":0,"id":257,"type":"SliderInput"},"259":{"min":0,"max":1,"value":1,"id":259,"type":"SliderInput"},"266":{"x":123,"y":207,"width":250,"elements":[267,273,274,271],"id":266,"type":"TimerEditor"},"267":{"outputLength":1,"title":"Timer","icon":"ti ti-clock","id":267,"type":"TitleElement"},"269":{"value":1.985,"id":269,"type":"NumberInput"},"270":{"value":0.37,"id":270,"type":"NumberInput"},"271":{"inputs":[272],"id":271,"type":"Element"},"272":{"value":"Reset","id":272,"type":"ButtonInput"},"273":{"inputs":[269],"id":273,"type":"Element"},"274":{"inputs":[270],"label":"Scale","id":274,"type":"LabelElement"},"282":{"x":485,"y":94,"width":250,"elements":[283,287,286],"id":282,"type":"OscillatorEditor"},"283":{"outputLength":1,"title":"Oscillator","id":283,"type":"TitleElement"},"285":{"options":[{"name":"Sine","value":"sine"},{"name":"Square","value":"square"},{"name":"Triangle","value":"triangle"},{"name":"Sawtooth","value":"sawtooth"}],"value":"sine","id":285,"type":"SelectInput"},"286":{"inputLength":1,"links":[267],"label":"Time","id":286,"type":"LabelElement"},"287":{"inputs":[285],"id":287,"type":"Element"},"292":{"x":1208,"y":66,"width":200,"elements":[293,295,296,297],"id":292,"type":"BlendEditor"},"293":{"outputLength":3,"title":"Blend","id":293,"type":"TitleElement"},"295":{"inputLength":3,"links":[303],"label":"Base","id":295,"type":"LabelElement"},"296":{"inputLength":3,"links":[323],"label":"Blend","id":296,"type":"LabelElement"},"297":{"inputLength":1,"links":[283],"label":"Opacity","id":297,"type":"LabelElement"},"302":{"x":797,"y":-55,"width":300,"elements":[303,310,311,312],"id":302,"type":"ColorEditor"},"303":{"outputLength":1,"title":"Color","icon":"ti ti-palette","id":303,"type":"TitleElement"},"305":{"value":16580865,"id":305,"type":"ColorInput"},"306":{"value":"#FD0101","id":306,"type":"StringInput"},"307":{"min":0,"max":1,"step":0.01,"value":0.996,"id":307,"type":"NumberInput"},"308":{"min":0,"max":1,"step":0.01,"value":0.004,"id":308,"type":"NumberInput"},"309":{"min":0,"max":1,"step":0.01,"value":0.004,"id":309,"type":"NumberInput"},"310":{"inputs":[305],"id":310,"type":"Element"},"311":{"inputs":[306],"label":"Hex","id":311,"type":"LabelElement"},"312":{"inputs":[307,308,309],"label":"RGB","id":312,"type":"LabelElement"},"322":{"x":810,"y":220,"width":300,"elements":[323,330,331,332],"id":322,"type":"ColorEditor"},"323":{"outputLength":1,"title":"Color","icon":"ti ti-palette","id":323,"type":"TitleElement"},"325":{"value":19455,"id":325,"type":"ColorInput"},"326":{"value":"#004BFF","id":326,"type":"StringInput"},"327":{"min":0,"max":1,"step":0.01,"value":0,"id":327,"type":"NumberInput"},"328":{"min":0,"max":1,"step":0.01,"value":0.298,"id":328,"type":"NumberInput"},"329":{"min":0,"max":1,"step":0.01,"value":1,"id":329,"type":"NumberInput"},"330":{"inputs":[325],"id":330,"type":"Element"},"331":{"inputs":[326],"label":"Hex","id":331,"type":"LabelElement"},"332":{"inputs":[327,328,329],"label":"RGB","id":332,"type":"LabelElement"},"371":{"x":2064,"y":-17,"width":300,"elements":[372,375,385,386,387,388],"id":371,"type":"MeshEditor"},"372":{"outputLength":1,"title":"Mesh","id":372,"type":"TitleElement"},"374":{"value":"Stanford_Bunny","id":374,"type":"StringInput"},"375":{"inputs":[374],"label":"Name","id":375,"type":"LabelElement"},"376":{"value":0,"id":376,"type":"NumberInput"},"377":{"value":0,"id":377,"type":"NumberInput"},"378":{"value":10,"id":378,"type":"NumberInput"},"379":{"value":0,"id":379,"type":"NumberInput"},"380":{"value":0,"id":380,"type":"NumberInput"},"381":{"value":0,"id":381,"type":"NumberInput"},"382":{"value":100,"id":382,"type":"NumberInput"},"383":{"value":100,"id":383,"type":"NumberInput"},"384":{"value":100,"id":384,"type":"NumberInput"},"385":{"inputs":[376,377,378],"label":"Position","id":385,"type":"LabelElement"},"386":{"inputs":[379,380,381],"label":"Rotation","id":386,"type":"LabelElement"},"387":{"inputs":[382,383,384],"label":"Scale","id":387,"type":"LabelElement"},"388":{"inputLength":1,"links":[248],"label":"Material","id":388,"type":"LabelElement"}},"nodes":[247,266,282,292,302,322,371],"id":2,"type":"Canvas"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,95 @@
import { ColorInput, StringInput, NumberInput, LabelElement, Element } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { ColorNode } from 'three-nodes/Nodes.js';
export class ColorEditor extends BaseNode {
constructor() {
const node = new ColorNode();
super( 'Color', 3, node );
const updateFields = ( editing ) => {
const value = node.value;
const hexValue = value.getHex();
const hexString = hexValue.toString( 16 ).toUpperCase().padStart( 6, '0' );
if ( editing !== 'color' ) {
field.setValue( hexValue, false );
}
if ( editing !== 'hex' ) {
hexField.setValue( '#' + hexString, false );
}
if ( editing !== 'rgb' ) {
fieldR.setValue( value.r.toFixed( 3 ), false );
fieldG.setValue( value.g.toFixed( 3 ), false );
fieldB.setValue( value.b.toFixed( 3 ), false );
}
fieldR.setTagColor( `#${hexString.slice( 0, 2 )}0000` );
fieldG.setTagColor( `#00${hexString.slice( 2, 4 )}00` );
fieldB.setTagColor( `#0000${hexString.slice( 4, 6 )}` );
};
const field = new ColorInput( 0xFFFFFF ).onChange( () => {
node.value.setHex( field.getValue() );
updateFields( 'picker' );
} );
const hexField = new StringInput().onChange( () => {
const value = hexField.getValue();
if ( value.indexOf( '#' ) === 0 ) {
const hexStr = value.slice( 1 ).padEnd( 6, '0' );
node.value.setHex( parseInt( hexStr, 16 ) );
updateFields( 'hex' );
}
} );
hexField.addEventListener( 'blur', () => {
updateFields();
} );
const onChangeRGB = () => {
node.value.setRGB( fieldR.getValue(), fieldG.getValue(), fieldB.getValue() );
updateFields( 'rgb' );
};
const fieldR = new NumberInput( 1, 0, 1 ).setTagColor( 'red' ).onChange( onChangeRGB );
const fieldG = new NumberInput( 1, 0, 1 ).setTagColor( 'green' ).onChange( onChangeRGB );
const fieldB = new NumberInput( 1, 0, 1 ).setTagColor( 'blue' ).onChange( onChangeRGB );
this.add( new Element().add( field ).setSerializable( false ) )
.add( new LabelElement( 'Hex' ).add( hexField ).setSerializable( false ) )
.add( new LabelElement( 'RGB' ).add( fieldR ).add( fieldG ).add( fieldB ) );
updateFields();
}
}

View File

@@ -0,0 +1,23 @@
import { NumberInput, Element } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { FloatNode } from 'three-nodes/Nodes.js';
export class FloatEditor extends BaseNode {
constructor() {
const node = new FloatNode();
super( 'Float', 1, node, 150 );
const field = new NumberInput().setTagColor( 'red' ).onChange( () => {
node.value = field.getValue();
} );
this.add( new Element().add( field ) );
}
}

View File

@@ -0,0 +1,67 @@
import { ButtonInput, SliderInput, NumberInput, LabelElement, Element } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { FloatNode } from 'three-nodes/Nodes.js';
export class SliderEditor extends BaseNode {
constructor() {
const node = new FloatNode();
super( 'Slider', 1, node );
this.collapse = true;
const field = new SliderInput( 0, 0, 1 ).onChange( () => {
node.value = field.getValue();
} );
const updateRange = () => {
const min = minInput.getValue();
const max = maxInput.getValue();
if ( min <= max ) {
field.setRange( min, max );
} else {
maxInput.setValue( min );
}
};
const minInput = new NumberInput().onChange( updateRange );
const maxInput = new NumberInput( 1 ).onChange( updateRange );
const minElement = new LabelElement( 'Min.' ).add( minInput ).setVisible( false );
const maxElement = new LabelElement( 'Max.' ).add( maxInput ).setVisible( false );
const moreElement = new Element().add( new ButtonInput( 'More' ).onClick( () => {
minElement.setVisible( true );
maxElement.setVisible( true );
moreElement.setVisible( false );
} ) ).setSerializable( false );
this.add( new Element().add( field ) )
.add( minElement )
.add( maxElement )
.add( moreElement );
this.onBlur( () => {
minElement.setVisible( false );
maxElement.setVisible( false );
moreElement.setVisible( true );
} );
}
}

View File

@@ -0,0 +1,145 @@
import { LabelElement, ToggleInput, SelectInput } from '../../libs/flow.module.js';
import { BaseNode, onNodeValidElement } from '../core/BaseNode.js';
import { TextureNode, UVNode } from 'three-nodes/Nodes.js';
import { Texture, TextureLoader, RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping } from 'three';
const fileTexture = new WeakMap();
const textureLoader = new TextureLoader();
const defaultTexture = new Texture();
const defaultUV = new UVNode();
const getTextureFromFile = ( file ) => {
let texture = fileTexture.get( file );
if ( texture === undefined ) {
texture = textureLoader.load( URL.createObjectURL( file ) );
fileTexture.set( file, texture );
}
return texture;
};
export class TextureEditor extends BaseNode {
constructor() {
const node = new TextureNode( defaultTexture );
super( 'Texture', 4, node, 250 );
this.texture = null;
this._initFile();
this._initParams();
this.onValidElement = () => {};
}
_initFile() {
const fileElement = new LabelElement( 'File' ).setInputColor( 'aqua' ).setInput( 1 );
fileElement.onValid( ( source, target, stage ) => {
const object = target.getObject();
if ( object && ( object instanceof File ) === false ) {
if ( stage === 'dragged' ) {
const name = target.node.getName();
this.editor.tips.error( `"${name}" is not a File.` );
}
return false;
}
} ).onConnect( () => {
const file = fileElement.getLinkedObject();
const node = this.value;
this.texture = file ? getTextureFromFile( file ) : null;
node.value = this.texture || defaultTexture;
this.update();
} );
this.add( fileElement );
}
_initParams() {
const uvField = new LabelElement( 'UV' ).setInput( 2 );
uvField.onValid( onNodeValidElement ).onConnect( () => {
const node = this.value;
node.uvNode = uvField.getLinkedObject() || defaultUV;
} );
this.wrapSInput = new SelectInput( [
{ name: 'Repeat Wrapping', value: RepeatWrapping },
{ name: 'Clamp To Edge Wrapping', value: ClampToEdgeWrapping },
{ name: 'Mirrored Repeat Wrapping', value: MirroredRepeatWrapping }
], RepeatWrapping ).onChange( () => {
this.update();
} );
this.wrapTInput = new SelectInput( [
{ name: 'Repeat Wrapping', value: RepeatWrapping },
{ name: 'Clamp To Edge Wrapping', value: ClampToEdgeWrapping },
{ name: 'Mirrored Repeat Wrapping', value: MirroredRepeatWrapping }
], RepeatWrapping ).onChange( () => {
this.update();
} );
this.flipYInput = new ToggleInput( false ).onClick( () => {
this.update();
} );
this.add( uvField )
.add( new LabelElement( 'Wrap S' ).add( this.wrapSInput ) )
.add( new LabelElement( 'Wrap T' ).add( this.wrapTInput ) )
.add( new LabelElement( 'Flip Y' ).add( this.flipYInput ) );
}
update() {
const texture = this.texture;
if ( texture ) {
texture.wrapS = Number( this.wrapSInput.getValue() );
texture.wrapT = Number( this.wrapTInput.getValue() );
texture.flipY = this.flipYInput.getValue();
texture.dispose();
this.invalidate();
}
}
}

View File

@@ -0,0 +1,27 @@
import { NumberInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { Vector2Node } from 'three-nodes/Nodes.js';
export class Vector2Editor extends BaseNode {
constructor() {
const node = new Vector2Node();
super( 'Vector 2', 2, node );
const onUpdate = () => {
node.value.x = fieldX.getValue();
node.value.y = fieldY.getValue();
};
const fieldX = new NumberInput().setTagColor( 'red' ).onChange( onUpdate );
const fieldY = new NumberInput().setTagColor( 'green' ).onChange( onUpdate );
this.add( new LabelElement( 'XY' ).add( fieldX ).add( fieldY ) );
}
}

View File

@@ -0,0 +1,29 @@
import { NumberInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { Vector3Node } from 'three-nodes/Nodes.js';
export class Vector3Editor extends BaseNode {
constructor() {
const node = new Vector3Node();
super( 'Vector 3', 3, node, 325 );
const onUpdate = () => {
node.value.x = fieldX.getValue();
node.value.y = fieldY.getValue();
node.value.z = fieldZ.getValue();
};
const fieldX = new NumberInput().setTagColor( 'red' ).onChange( onUpdate );
const fieldY = new NumberInput().setTagColor( 'green' ).onChange( onUpdate );
const fieldZ = new NumberInput().setTagColor( 'blue' ).onChange( onUpdate );
this.add( new LabelElement( 'XYZ' ).add( fieldX ).add( fieldY ).add( fieldZ ) );
}
}

View File

@@ -0,0 +1,36 @@
import { NumberInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { Vector4Node } from 'three-nodes/Nodes.js';
export class Vector4Editor extends BaseNode {
constructor() {
const node = new Vector4Node();
super( 'Vector 4', 4, node, 350 );
const onUpdate = () => {
node.value.x = fieldX.getValue();
node.value.y = fieldY.getValue();
node.value.z = fieldZ.getValue();
node.value.w = fieldW.getValue();
};
const fieldX = new NumberInput().setTagColor( 'red' ).onChange( onUpdate );
const fieldY = new NumberInput().setTagColor( 'green' ).onChange( onUpdate );
const fieldZ = new NumberInput().setTagColor( 'blue' ).onChange( onUpdate );
const fieldW = new NumberInput( 1 ).setTagColor( 'white' ).onChange( onUpdate );
this.add( new LabelElement( 'XYZW' )
.add( fieldX )
.add( fieldY )
.add( fieldZ )
.add( fieldW )
);
}
}

View File

@@ -0,0 +1,87 @@
import { ColorInput, SliderInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MeshBasicNodeMaterial } from 'three-nodes/Nodes.js';
import { MathUtils } from 'three';
export class BasicMaterialEditor extends BaseNode {
constructor() {
const material = new MeshBasicNodeMaterial();
super( 'Basic Material', 1, material );
this.setWidth( 300 );
const color = new LabelElement( 'color' ).setInput( 3 );
const opacity = new LabelElement( 'opacity' ).setInput( 1 );
const position = new LabelElement( 'position' ).setInput( 3 );
color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => {
material.color.setHex( input.getValue() );
} ) );
opacity.add( new SliderInput( material.opacity, 0, 1 ).onChange( ( input ) => {
material.opacity = input.getValue();
this.updateTransparent();
} ) );
color.onConnect( () => this.update(), true );
opacity.onConnect( () => this.update(), true );
position.onConnect( () => this.update(), true );
this.add( color )
.add( opacity )
.add( position );
this.color = color;
this.opacity = opacity;
this.position = position;
this.material = material;
this.update();
}
update() {
const { material, color, opacity, position } = this;
color.setEnabledInputs( ! color.getLinkedObject() );
opacity.setEnabledInputs( ! opacity.getLinkedObject() );
material.colorNode = color.getLinkedObject();
material.opacityNode = opacity.getLinkedObject() || null;
material.positionNode = position.getLinkedObject() || null;
material.dispose();
this.updateTransparent();
// TODO: Fix on NodeMaterial System
material.customProgramCacheKey = () => {
return MathUtils.generateUUID();
};
}
updateTransparent() {
const { material, opacity } = this;
material.transparent = opacity.getLinkedObject() || material.opacity < 1 ? true : false;
opacity.setIcon( material.transparent ? 'ti ti-layers-intersect' : 'ti ti-layers-subtract' );
}
}

View File

@@ -0,0 +1,102 @@
import { ColorInput, ToggleInput, SliderInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { PointsNodeMaterial } from 'three-nodes/Nodes.js';
import * as THREE from 'three';
export class PointsMaterialEditor extends BaseNode {
constructor() {
const material = new PointsNodeMaterial();
super( 'Points Material', 1, material );
this.setWidth( 300 );
const color = new LabelElement( 'color' ).setInput( 3 );
const opacity = new LabelElement( 'opacity' ).setInput( 1 );
const size = new LabelElement( 'size' ).setInput( 1 );
const position = new LabelElement( 'position' ).setInput( 3 );
const sizeAttenuation = new LabelElement( 'Size Attenuation' );
color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => {
material.color.setHex( input.getValue() );
} ) );
opacity.add( new SliderInput( material.opacity, 0, 1 ).onChange( ( input ) => {
material.opacity = input.getValue();
this.updateTransparent();
} ) );
sizeAttenuation.add( new ToggleInput( material.sizeAttenuation ).onClick( ( input ) => {
material.sizeAttenuation = input.getValue();
material.dispose();
} ) );
color.onConnect( () => this.update(), true );
opacity.onConnect( () => this.update(), true );
size.onConnect( () => this.update(), true );
position.onConnect( () => this.update(), true );
this.add( color )
.add( opacity )
.add( size )
.add( position )
.add( sizeAttenuation );
this.color = color;
this.opacity = opacity;
this.size = size;
this.position = position;
this.sizeAttenuation = sizeAttenuation;
this.material = material;
this.update();
}
update() {
const { material, color, opacity, size, position } = this;
color.setEnabledInputs( ! color.getLinkedObject() );
opacity.setEnabledInputs( ! opacity.getLinkedObject() );
material.colorNode = color.getLinkedObject();
material.opacityNode = opacity.getLinkedObject() || null;
material.sizeNode = size.getLinkedObject() || null;
material.positionNode = position.getLinkedObject() || null;
material.dispose();
this.updateTransparent();
// TODO: Fix on NodeMaterial System
material.customProgramCacheKey = () => {
return THREE.MathUtils.generateUUID();
};
}
updateTransparent() {
const { material, opacity } = this;
material.transparent = opacity.getLinkedObject() || material.opacity < 1 ? true : false;
opacity.setIcon( material.transparent ? 'ti ti-layers-intersect' : 'ti ti-layers-subtract' );
}
}

View File

@@ -0,0 +1,121 @@
import { ColorInput, SliderInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MeshStandardNodeMaterial } from 'three-nodes/Nodes.js';
import * as THREE from 'three';
export class StandardMaterialEditor extends BaseNode {
constructor() {
const material = new MeshStandardNodeMaterial();
super( 'Standard Material', 1, material );
this.setWidth( 300 );
const color = new LabelElement( 'color' ).setInput( 3 );
const opacity = new LabelElement( 'opacity' ).setInput( 1 );
const metalness = new LabelElement( 'metalness' ).setInput( 1 );
const roughness = new LabelElement( 'roughness' ).setInput( 1 );
const emissive = new LabelElement( 'emissive' ).setInput( 3 );
const normal = new LabelElement( 'normal' ).setInput( 3 );
const position = new LabelElement( 'position' ).setInput( 3 );
color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => {
material.color.setHex( input.getValue() );
} ) );
opacity.add( new SliderInput( material.opacity, 0, 1 ).onChange( ( input ) => {
material.opacity = input.getValue();
this.updateTransparent();
} ) );
metalness.add( new SliderInput( material.metalness, 0, 1 ).onChange( ( input ) => {
material.metalness = input.getValue();
} ) );
roughness.add( new SliderInput( material.roughness, 0, 1 ).onChange( ( input ) => {
material.roughness = input.getValue();
} ) );
color.onConnect( () => this.update(), true );
opacity.onConnect( () => this.update(), true );
metalness.onConnect( () => this.update(), true );
roughness.onConnect( () => this.update(), true );
emissive.onConnect( () => this.update(), true );
normal.onConnect( () => this.update(), true );
position.onConnect( () => this.update(), true );
this.add( color )
.add( opacity )
.add( metalness )
.add( roughness )
.add( emissive )
.add( normal )
.add( position );
this.color = color;
this.opacity = opacity;
this.metalness = metalness;
this.roughness = roughness;
this.emissive = emissive;
this.normal = normal;
this.position = position;
this.material = material;
this.update();
}
update() {
const { material, color, opacity, emissive, roughness, metalness, normal, position } = this;
color.setEnabledInputs( ! color.getLinkedObject() );
opacity.setEnabledInputs( ! opacity.getLinkedObject() );
roughness.setEnabledInputs( ! roughness.getLinkedObject() );
metalness.setEnabledInputs( ! metalness.getLinkedObject() );
material.colorNode = color.getLinkedObject();
material.opacityNode = opacity.getLinkedObject();
material.metalnessNode = metalness.getLinkedObject();
material.roughnessNode = roughness.getLinkedObject();
material.emissiveNode = emissive.getLinkedObject();
material.normalNode = normal.getLinkedObject();
material.positionNode = position.getLinkedObject();
material.dispose();
this.updateTransparent();
// TODO: Fix on NodeMaterial System
material.customProgramCacheKey = () => {
return THREE.MathUtils.generateUUID();
};
}
updateTransparent() {
const { material, opacity } = this;
material.transparent = opacity.getLinkedObject() || material.opacity < 1 ? true : false;
opacity.setIcon( material.transparent ? 'ti ti-layers-intersect' : 'ti ti-layers-subtract' );
}
}

View File

@@ -0,0 +1,39 @@
import { SelectInput, Element, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MathNode, Vector3Node } from 'three-nodes/Nodes.js';
const DEFAULT_VALUE = new Vector3Node();
export class AngleEditor extends BaseNode {
constructor() {
const node = new MathNode( MathNode.SIN, DEFAULT_VALUE );
super( 'Angle', 1, node, 175 );
const optionsField = new SelectInput( [
{ name: 'Degrees to Radians', value: MathNode.RAD },
{ name: 'Radians to Degrees', value: MathNode.DEG }
], MathNode.RAD ).onChange( () => {
node.method = optionsField.getValue();
this.invalidate();
} );
const input = new LabelElement( 'A' ).setInput( 1 );
input.onConnect( () => {
node.aNode = input.getLinkedObject() || DEFAULT_VALUE;
} );
this.add( new Element().add( optionsField ) )
.add( input );
}
}

View File

@@ -0,0 +1,35 @@
import { LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MathNode, FloatNode } from 'three-nodes/Nodes.js';
const NULL_VALUE = new FloatNode();
export class DotEditor extends BaseNode {
constructor() {
const node = new MathNode( MathNode.DOT, NULL_VALUE, NULL_VALUE );
super( 'Dot Product', 1, node, 175 );
const aElement = new LabelElement( 'A' ).setInput( 3 );
const bElement = new LabelElement( 'B' ).setInput( 3 );
aElement.onConnect( () => {
node.aNode = aElement.getLinkedObject() || NULL_VALUE;
} );
bElement.onConnect( () => {
node.bNode = bElement.getLinkedObject() || NULL_VALUE;
} );
this.add( aElement )
.add( bElement );
}
}

View File

@@ -0,0 +1,39 @@
import { SelectInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MathNode, FloatNode } from 'three-nodes/Nodes.js';
const DEFAULT_VALUE = new FloatNode();
export class InvertEditor extends BaseNode {
constructor() {
const node = new MathNode( MathNode.INVERT, DEFAULT_VALUE );
super( 'Invert / Negate', 1, node, 175 );
const optionsField = new SelectInput( [
{ name: 'Invert ( 1 - Source )', value: MathNode.INVERT },
{ name: 'Negate ( - Source )', value: MathNode.NEGATE }
], MathNode.INVERT ).onChange( () => {
node.method = optionsField.getValue();
this.invalidate();
} );
const input = new LabelElement( 'Source' ).setInput( 1 );
input.onConnect( () => {
node.aNode = input.getLinkedObject() || DEFAULT_VALUE;
} );
this.add( new LabelElement( 'Method' ).add( optionsField ) )
.add( input );
}
}

View File

@@ -0,0 +1,63 @@
import { SelectInput, LabelElement, Element, NumberInput } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MathNode, FloatNode } from 'three-nodes/Nodes.js';
export class LimiterEditor extends BaseNode {
constructor() {
const NULL_VALUE = new FloatNode();
const node = new MathNode( MathNode.MIN, NULL_VALUE, NULL_VALUE );
super( 'Limiter', 1, node, 175 );
const methodInput = new SelectInput( [
{ name: 'Min', value: MathNode.MIN },
{ name: 'Max', value: MathNode.MAX },
// { name: 'Clamp', value: MathNode.CLAMP }
{ name: 'Saturate', value: MathNode.SATURATE }
], MathNode.MIN );
methodInput.onChange( ( data ) => {
node.method = data.getValue();
bElement.setVisible( data.getValue() !== MathNode.SATURATE );
this.invalidate();
} );
const aElement = new LabelElement( 'A' ).setInput( 1 );
const bElement = new LabelElement( 'B' ).setInput( 1 );
aElement.add( new NumberInput().onChange( ( field ) => {
node.aNode.value = field.getValue();
} ) ).onConnect( ( elmt ) => {
elmt.setEnabledInputs( ! elmt.getLinkedObject() );
node.aNode = elmt.getLinkedObject() || NULL_VALUE;
} );
bElement.add( new NumberInput().onChange( ( field ) => {
node.bNode.value = field.getValue();
} ) ).onConnect( ( elmt ) => {
elmt.setEnabledInputs( ! elmt.getLinkedObject() );
node.bNode = elmt.getLinkedObject() || NULL_VALUE;
} );
this.add( new Element().add( methodInput ) )
.add( aElement )
.add( bElement );
}
}

View File

@@ -0,0 +1,27 @@
import { LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MathNode, Vector3Node } from 'three-nodes/Nodes.js';
const DEFAULT_VALUE = new Vector3Node();
export class NormalizeEditor extends BaseNode {
constructor() {
const node = new MathNode( MathNode.NORMALIZE, DEFAULT_VALUE );
super( 'Normalize', 3, node, 175 );
const input = new LabelElement( 'A' ).setInput( 3 );
input.onConnect( () => {
node.aNode = input.getLinkedObject() || DEFAULT_VALUE;
} );
this.add( input );
}
}

View File

@@ -0,0 +1,64 @@
import { Element, LabelElement, NumberInput, SelectInput } from '../../libs/flow.module.js';
import { FloatNode, OperatorNode } from 'three-nodes/Nodes.js';
import { BaseNode } from '../core/BaseNode.js';
export class OperatorEditor extends BaseNode {
constructor() {
const NULL_VALUE = new FloatNode();
const node = new OperatorNode( '+', NULL_VALUE, NULL_VALUE );
super( 'Operator', 1, node, 150 );
const opInput = new SelectInput( [
{ name: 'Addition ( + )', value: '+' },
{ name: 'Subtraction ( - )', value: '-' },
{ name: 'Multiplication ( * )', value: '*' },
{ name: 'Division ( / )', value: '/' }
], '+' );
opInput.onChange( ( data ) => {
node.op = data.getValue();
this.invalidate();
} );
const aElement = new LabelElement( 'A' ).setInput( 3 );
const bElement = new LabelElement( 'B' ).setInput( 3 );
aElement.add( new NumberInput().onChange( ( field ) => {
node.aNode.value = field.getValue();
} ) ).onConnect( ( elmt ) => {
elmt.setEnabledInputs( ! elmt.getLinkedObject() );
node.aNode = elmt.getLinkedObject() || NULL_VALUE;
} );
bElement.add( new NumberInput().onChange( ( field ) => {
node.bNode.value = field.getValue();
} ) ).onConnect( ( elmt ) => {
elmt.setEnabledInputs( ! elmt.getLinkedObject() );
node.bNode = elmt.getLinkedObject() || NULL_VALUE;
} );
this.add( new Element().add( opInput ) )
.add( aElement )
.add( bElement );
}
}

View File

@@ -0,0 +1,45 @@
import { LabelElement, NumberInput } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MathNode, FloatNode } from 'three-nodes/Nodes.js';
export class PowerEditor extends BaseNode {
constructor() {
const NULL_VALUE = new FloatNode();
const node = new MathNode( MathNode.POW, NULL_VALUE, NULL_VALUE );
super( 'Power', 1, node, 175 );
const aElement = new LabelElement( 'A' ).setInput( 1 );
const bElement = new LabelElement( 'B' ).setInput( 1 );
aElement.add( new NumberInput().onChange( ( field ) => {
node.aNode.value = field.getValue();
} ) ).onConnect( ( elmt ) => {
elmt.setEnabledInputs( ! elmt.getLinkedObject() );
node.aNode = elmt.getLinkedObject() || NULL_VALUE;
} );
bElement.add( new NumberInput().onChange( ( field ) => {
node.bNode.value = field.getValue();
} ) ).onConnect( ( elmt ) => {
elmt.setEnabledInputs( ! elmt.getLinkedObject() );
node.bNode = elmt.getLinkedObject() || NULL_VALUE;
} );
this.add( aElement )
.add( bElement );
}
}

View File

@@ -0,0 +1,44 @@
import { SelectInput, Element, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MathNode, Vector3Node } from 'three-nodes/Nodes.js';
const DEFAULT_VALUE = new Vector3Node();
export class TrigonometryEditor extends BaseNode {
constructor() {
const node = new MathNode( MathNode.SIN, DEFAULT_VALUE );
super( 'Trigonometry', 1, node, 175 );
const optionsField = new SelectInput( [
{ name: 'Sin', value: MathNode.SIN },
{ name: 'Cos', value: MathNode.COS },
{ name: 'Tan', value: MathNode.TAN },
{ name: 'asin', value: MathNode.ASIN },
{ name: 'acos', value: MathNode.ACOS },
{ name: 'atan', value: MathNode.ATAN }
], MathNode.SIN ).onChange( () => {
node.method = optionsField.getValue();
this.invalidate();
} );
const input = new LabelElement( 'A' ).setInput( 1 );
input.onConnect( () => {
node.aNode = input.getLinkedObject() || DEFAULT_VALUE;
} );
this.add( new Element().add( optionsField ) )
.add( input );
}
}

View File

@@ -0,0 +1,27 @@
import { LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { CheckerNode, UVNode } from 'three-nodes/Nodes.js';
const defaultUV = new UVNode();
export class CheckerEditor extends BaseNode {
constructor() {
const node = new CheckerNode( defaultUV );
super( 'Checker', 1, node, 200 );
const field = new LabelElement( 'UV' ).setInput( 2 );
field.onConnect( () => {
node.uvNode = field.getLinkedObject() || defaultUV;
} );
this.add( field );
}
}

View File

@@ -0,0 +1,99 @@
import { LabelElement } from '../../libs/flow.module.js';
import { Object3DEditor } from './Object3DEditor.js';
import { Mesh } from 'three';
export class MeshEditor extends Object3DEditor {
constructor( mesh = null ) {
if ( mesh === null ) {
mesh = new Mesh();
}
super( mesh, 'Mesh' );
this.material = null;
this.defaultMaterial = null;
this._initMaterial();
this.updateDefault();
this.restoreDefault();
this.update();
}
get mesh() {
return this.value;
}
_initMaterial() {
const materialElement = new LabelElement( 'Material' ).setInputColor( 'forestgreen' ).setInput( 1 );
materialElement.onValid( ( source, target, stage ) => {
const object = target.getObject();
if ( object && object.isMaterial !== true ) {
if ( stage === 'dragged' ) {
const name = target.node.getName();
this.editor.tips.error( `"${name}" is not a Material.` );
}
return false;
}
} ).onConnect( () => {
this.material = materialElement.getLinkedObject() || this.defaultMaterial;
this.update();
} );
this.add( materialElement );
}
update() {
super.update();
const mesh = this.mesh;
if ( mesh ) {
mesh.material = this.material || this.defaultMaterial;
}
}
updateDefault() {
super.updateDefault();
this.defaultMaterial = this.mesh.material;
}
restoreDefault() {
super.restoreDefault();
this.mesh.material = this.defaultMaterial;
}
}

View File

@@ -0,0 +1,160 @@
import { NumberInput, StringInput, LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { Group, MathUtils, Vector3 } from 'three';
export class Object3DEditor extends BaseNode {
constructor( object3d = null, name = 'Object 3D' ) {
if ( object3d === null ) {
object3d = new Group();
}
super( name, 1, object3d );
this.defaultPosition = new Vector3();
this.defaultRotation = new Vector3();
this.defaultScale = new Vector3( 100, 100, 100 );
this._initTags();
this._initTransform();
this.onValidElement = () => {};
}
setEditor( editor ) {
if ( this.editor ) {
this.restoreDefault();
}
super.setEditor( editor );
if ( editor ) {
const name = this.nameInput.getValue();
const object3d = editor.scene.getObjectByName( name );
this.value = object3d;
this.updateDefault();
this.restoreDefault();
this.update();
}
return this;
}
get object3d() {
return this.value;
}
_initTags() {
this.nameInput = new StringInput( this.object3d.name ).setReadOnly( true )
.onChange( () => this.object3d.name = this.nameInput.getValue() );
this.add( new LabelElement( 'Name' ).add( this.nameInput ) );
}
_initTransform() {
const update = () => this.update();
const posX = new NumberInput().setTagColor( 'red' ).onChange( update );
const posY = new NumberInput().setTagColor( 'green' ).onChange( update );
const posZ = new NumberInput().setTagColor( 'blue' ).onChange( update );
const rotationStep = 1;
const rotX = new NumberInput().setTagColor( 'red' ).setStep( rotationStep ).onChange( update );
const rotY = new NumberInput().setTagColor( 'green' ).setStep( rotationStep ).onChange( update );
const rotZ = new NumberInput().setTagColor( 'blue' ).setStep( rotationStep ).onChange( update );
const scaleX = new NumberInput( 100 ).setTagColor( 'red' ).setStep( rotationStep ).onChange( update );
const scaleY = new NumberInput( 100 ).setTagColor( 'green' ).setStep( rotationStep ).onChange( update );
const scaleZ = new NumberInput( 100 ).setTagColor( 'blue' ).setStep( rotationStep ).onChange( update );
this.add( new LabelElement( 'Position' ).add( posX ).add( posY ).add( posZ ) )
.add( new LabelElement( 'Rotation' ).add( rotX ).add( rotY ).add( rotZ ) )
.add( new LabelElement( 'Scale' ).add( scaleX ).add( scaleY ).add( scaleZ ) );
this.posX = posX;
this.posY = posY;
this.posZ = posZ;
this.rotX = rotX;
this.rotY = rotY;
this.rotZ = rotZ;
this.scaleX = scaleX;
this.scaleY = scaleY;
this.scaleZ = scaleZ;
}
update() {
const object3d = this.object3d;
if ( object3d ) {
const { position, rotation, scale } = object3d;
position.x = this.posX.getValue();
position.y = this.posY.getValue();
position.z = this.posZ.getValue();
rotation.x = MathUtils.degToRad( this.rotX.getValue() );
rotation.y = MathUtils.degToRad( this.rotY.getValue() );
rotation.z = MathUtils.degToRad( this.rotZ.getValue() );
scale.x = this.scaleX.getValue() / 100;
scale.y = this.scaleY.getValue() / 100;
scale.z = this.scaleZ.getValue() / 100;
}
}
updateDefault() {
const { position, rotation, scale } = this.object3d;
this.defaultPosition = position.clone();
this.defaultRotation = new Vector3( MathUtils.radToDeg( rotation.x ), MathUtils.radToDeg( rotation.y ), MathUtils.radToDeg( rotation.z ) );
this.defaultScale = scale.clone().multiplyScalar( 100 );
}
restoreDefault() {
const position = this.defaultPosition;
const rotation = this.defaultRotation;
const scale = this.defaultScale;
this.posX.setValue( position.x );
this.posY.setValue( position.y );
this.posZ.setValue( position.z );
this.rotX.setValue( rotation.x );
this.rotY.setValue( rotation.y );
this.rotZ.setValue( rotation.z );
this.scaleX.setValue( scale.x );
this.scaleY.setValue( scale.y );
this.scaleZ.setValue( scale.z );
}
}

View File

@@ -0,0 +1,99 @@
import { LabelElement } from '../../libs/flow.module.js';
import { Object3DEditor } from './Object3DEditor.js';
import { Points } from 'three';
export class PointsEditor extends Object3DEditor {
constructor( points = null ) {
if ( points === null ) {
points = new Points();
}
super( points, 'Points' );
this.material = null;
this.defaultMaterial = null;
this._initMaterial();
this.updateDefault();
this.restoreDefault();
this.update();
}
get points() {
return this.value;
}
_initMaterial() {
const materialElement = new LabelElement( 'Material' ).setInputColor( 'forestgreen' ).setInput( 1 );
materialElement.onValid( ( source, target, stage ) => {
const object = target.getObject();
if ( object && object.isMaterial !== true ) {
if ( stage === 'dragged' ) {
const name = target.node.getName();
this.editor.tips.error( `"${name}" is not a Material.` );
}
return false;
}
} ).onConnect( () => {
this.material = materialElement.getLinkedObject() || this.defaultMaterial;
this.update();
} );
this.add( materialElement );
}
update() {
super.update();
const points = this.points;
if ( points ) {
points.material = this.material || this.defaultMaterial;
}
}
updateDefault() {
super.updateDefault();
this.defaultMaterial = this.points.material;
}
restoreDefault() {
super.restoreDefault();
this.points.material = this.defaultMaterial;
}
}

View File

@@ -0,0 +1,58 @@
import { LabelElement } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { JoinNode, FloatNode } from 'three-nodes/Nodes.js';
const NULL_VALUE = new FloatNode();
export class JoinEditor extends BaseNode {
constructor() {
const node = new JoinNode();
super( 'Join', 1, node, 175 );
const update = () => {
const values = [
xElement.getLinkedObject(),
yElement.getLinkedObject(),
zElement.getLinkedObject(),
wElement.getLinkedObject()
];
let length = 1;
if ( values[ 3 ] !== null ) length = 4;
else if ( values[ 2 ] !== null ) length = 3;
else if ( values[ 1 ] !== null ) length = 2;
const nodes = [];
for ( let i = 0; i < length; i ++ ) {
nodes.push( values[ i ] || NULL_VALUE );
}
node.nodes = nodes;
this.invalidate();
};
const xElement = new LabelElement( 'X | R' ).setInput( 1 ).onConnect( update );
const yElement = new LabelElement( 'Y | G' ).setInput( 1 ).onConnect( update );
const zElement = new LabelElement( 'Z | B' ).setInput( 1 ).onConnect( update );
const wElement = new LabelElement( 'W | A' ).setInput( 1 ).onConnect( update );
this.add( xElement )
.add( yElement )
.add( zElement )
.add( wElement );
update();
}
}

View File

@@ -0,0 +1,43 @@
import { SelectInput, LabelElement, Element } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { OscNode, FloatNode } from 'three-nodes/Nodes.js';
const NULL_VALUE = new FloatNode();
export class OscillatorEditor extends BaseNode {
constructor() {
const node = new OscNode( OscNode.SINE, NULL_VALUE );
super( 'Oscillator', 1, node, 175 );
const methodInput = new SelectInput( [
{ name: 'Sine', value: OscNode.SINE },
{ name: 'Square', value: OscNode.SQUARE },
{ name: 'Triangle', value: OscNode.TRIANGLE },
{ name: 'Sawtooth', value: OscNode.SAWTOOTH }
], OscNode.SINE );
methodInput.onChange( () => {
node.method = methodInput.getValue();
this.invalidate();
} );
const timeElement = new LabelElement( 'Time' ).setInput( 1 );
timeElement.onConnect( () => {
node.timeNode = timeElement.getLinkedObject() || NULL_VALUE;
} );
this.add( new Element().add( methodInput ) )
.add( timeElement );
}
}

View File

@@ -0,0 +1,166 @@
import { OrbitControls } from 'three-addons/controls/OrbitControls.js';
import { ViewHelper } from 'three-addons/helpers/ViewHelper.js';
import { Element, LabelElement, SelectInput } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { MeshBasicNodeMaterial, FloatNode } from 'three-nodes/Nodes.js';
import { WebGLRenderer, PerspectiveCamera, Scene, Mesh, DoubleSide, SphereGeometry, BoxGeometry, PlaneGeometry, TorusKnotGeometry } from 'three';
const nullValue = new FloatNode().setConst( true );
const sceneDict = {};
const getScene = ( name ) => {
let scene = sceneDict[ name ];
if ( scene === undefined ) {
scene = new Scene();
if ( name === 'box' ) {
const box = new Mesh( new BoxGeometry( 1.3, 1.3, 1.3 ) );
scene.add( box );
} else if ( name === 'sphere' ) {
const sphere = new Mesh( new SphereGeometry( 1, 32, 16 ) );
scene.add( sphere );
} else if ( name === 'plane' || name === 'sprite' ) {
const plane = new Mesh( new PlaneGeometry( 2, 2 ) );
scene.add( plane );
} else if ( name === 'torus' ) {
const torus = new Mesh( new TorusKnotGeometry( .7, .1, 100, 16 ) );
scene.add( torus );
}
sceneDict[ name ] = scene;
}
return scene;
};
export class PreviewEditor extends BaseNode {
constructor() {
const width = 300;
const height = 300;
super( 'Preview', 0, null, height );
const material = new MeshBasicNodeMaterial();
material.colorNode = nullValue;
material.side = DoubleSide;
material.transparent = true;
const previewElement = new Element();
previewElement.dom.style[ 'padding-top' ] = 0;
previewElement.dom.style[ 'padding-bottom' ] = 0;
const sceneInput = new SelectInput( [
{ name: 'Box', value: 'box' },
{ name: 'Sphere', value: 'sphere' },
{ name: 'Plane', value: 'plane' },
{ name: 'Sprite', value: 'sprite' },
{ name: 'Torus', value: 'torus' }
], 'box' );
const inputElement = new LabelElement( 'Input' ).setInput( 4 ).onConnect( () => {
material.colorNode = inputElement.getLinkedObject() || nullValue;
material.dispose();
}, true );
const canvas = document.createElement( 'canvas' );
canvas.style.position = 'absolute';
previewElement.dom.append( canvas );
previewElement.setHeight( height );
const renderer = new WebGLRenderer( {
canvas,
alpha: true
} );
renderer.autoClear = false;
renderer.setSize( width, height, true );
renderer.setPixelRatio( window.devicePixelRatio );
const camera = new PerspectiveCamera( 45, width / height, 0.1, 100 );
camera.aspect = width / height;
camera.updateProjectionMatrix();
camera.position.set( - 2, 2, 2 );
camera.lookAt( 0, 0, 0 );
const controls = new OrbitControls( camera, previewElement.dom );
controls.enableKeys = false;
controls.update();
const viewHelper = new ViewHelper( camera, previewElement.dom );
this.sceneInput = sceneInput;
this.viewHelper = viewHelper;
this.material = material;
this.camera = camera;
this.renderer = renderer;
this.add( inputElement )
.add( new LabelElement( 'Object' ).add( sceneInput ) )
.add( previewElement );
}
setEditor( editor ) {
super.setEditor( editor );
this.updateAnimationRequest();
}
updateAnimationRequest() {
if ( this.editor !== null ) {
requestAnimationFrame( () => this.update() );
}
}
update() {
const { viewHelper, material, renderer, camera, sceneInput } = this;
this.updateAnimationRequest();
const sceneName = sceneInput.getValue();
const scene = getScene( sceneName );
const mesh = scene.children[ 0 ];
mesh.material = material;
if ( sceneName === 'sprite' ) {
mesh.lookAt( camera.position );
}
renderer.clear();
renderer.render( scene, camera );
viewHelper.render( renderer );
}
}

View File

@@ -0,0 +1,39 @@
import { SelectInput, Element } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { SplitNode, FloatNode } from 'three-nodes/Nodes.js';
const NULL_VALUE = new FloatNode();
export class SplitEditor extends BaseNode {
constructor() {
const node = new SplitNode( NULL_VALUE, 'x' );
super( 'Split', 1, node, 175 );
const componentsField = new SelectInput( [
{ name: 'X | R', value: 'x' },
{ name: 'Y | G', value: 'y' },
{ name: 'Z | B', value: 'z' },
{ name: 'W | A', value: 'w' }
], node.components ).onChange( () => {
node.components = componentsField.getValue();
this.invalidate();
} );
const componentsElement = new Element().add( componentsField ).setInput( 1 )
.onConnect( () => {
node.node = componentsElement.getLinkedObject() || NULL_VALUE;
} );
this.add( componentsElement );
}
}

View File

@@ -0,0 +1,58 @@
import { NumberInput, LabelElement, Element, ButtonInput } from '../../libs/flow.module.js';
import { BaseNode } from '../core/BaseNode.js';
import { TimerNode } from 'three-nodes/Nodes.js';
export class TimerEditor extends BaseNode {
constructor() {
const node = new TimerNode();
super( 'Timer', 1, node, 200 );
this.title.setIcon( 'ti ti-clock' );
const updateField = () => {
field.setValue( node.value.toFixed( 3 ) );
};
const field = new NumberInput().onChange( () => {
node.value = field.getValue();
} );
const scaleField = new NumberInput( 1 ).onChange( () => {
node.scale = scaleField.getValue();
} );
const moreElement = new Element().add( new ButtonInput( 'Reset' ).onClick( () => {
node.value = 0;
updateField();
} ) ).setSerializable( false );
this.add( new Element().add( field ).setSerializable( false ) )
.add( new LabelElement( 'Speed' ).add( scaleField ) )
.add( moreElement );
// extends node
node._update = node.update;
node.update = function ( ...params ) {
this._update( ...params );
updateField();
};
}
}