浏览代码

Associated with #6012

David Catuhe 6 年之前
父节点
当前提交
794c4d46b5

+ 2 - 8
nodeEditor/src/components/diagram/generic/genericNodeModel.tsx

@@ -7,16 +7,13 @@ import { GraphEditor, NodeCreationOptions } from '../../../graphEditor';
 import { GlobalState } from '../../../globalState';
 import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
 import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
 
 /**
  * Generic node model which stores information about a node editor block
  */
 export class GenericNodeModel extends DefaultNodeModel {
 	/**
-	 * Labels for the block
-	 */
-    public header = "";
-	/**
 	 * Vector2 for the node if it exists
 	 */
     public vector2: Nullable<Vector2> = null;
@@ -41,10 +38,6 @@ export class GenericNodeModel extends DefaultNodeModel {
     }
 
     prepare(options: NodeCreationOptions, nodes: Array<DefaultNodeModel>, model: DiagramModel, graphEditor: GraphEditor, filterInputs: string[]) {
-        if (options.nodeMaterialBlock) {
-            this.header = options.nodeMaterialBlock.name;
-        }
-
         super.prepare(options, nodes, model, graphEditor, filterInputs);
     }
 
@@ -52,6 +45,7 @@ export class GenericNodeModel extends DefaultNodeModel {
 
         return (
             <LineContainerComponent title="GENERAL">
+                <TextInputLineComponent label="Name" propertyName="name" target={this.block!} onChange={() => globalState.onUpdateRequiredObservable.notifyObservers()} />
                 <TextLineComponent label="Type" value={this.block!.getClassName()} />
             </LineContainerComponent>
         );

+ 2 - 2
nodeEditor/src/components/diagram/generic/genericNodeWidget.tsx

@@ -48,8 +48,8 @@ export class GenericNodeWidget extends React.Component<GenericNodeWidgetProps, G
         var outputPorts = new Array<JSX.Element>()
         if (this.props.node) {
             // Header label
-            if (this.props.node.header) {
-                header = this.props.node.header;
+            if (this.props.node.block) {
+                header = this.props.node.block.name;
             }
 
             // Input/Output ports

+ 1 - 1
nodeEditor/src/components/diagram/texture/textureNodeWidget.tsx

@@ -72,7 +72,7 @@ export class TextureNodeWidget extends React.Component<TextureNodeWidgetProps> {
         return (
             <div className={"diagramBlock"}>
                 <div className="header">
-                    {`Texture (${this.props.node && this.props.node.texture ? this.props.node.texture.name : "not set"})`}
+                    {this.props.node!.block!.name}
                 </div>
                 <div className="inputs">
                     {inputPorts}

+ 3 - 0
nodeEditor/src/components/diagram/texture/texturePropertyTabComponent.tsx

@@ -8,6 +8,7 @@ import { Engine } from 'babylonjs/Engines/engine';
 import { TextureNodeModel } from './textureNodeModel';
 import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
 import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
+import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
 
 interface ITexturePropertyTabComponentProps {
     globalState: GlobalState;
@@ -49,6 +50,7 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
             }
 
             this.props.globalState.onUpdateRequiredObservable.notifyObservers();
+            this.props.globalState.onRebuildRequiredObservable.notifyObservers();
         }, undefined, true);
     }
 
@@ -56,6 +58,7 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
         return (
             <div>
                 <LineContainerComponent title="GENERAL">
+                    <TextInputLineComponent label="Name" propertyName="name" target={this.props.node.block!} onChange={() => this.props.globalState.onUpdateRequiredObservable.notifyObservers()} />
                     <TextLineComponent label="Type" value="Texture" />
                 </LineContainerComponent>
 

+ 15 - 0
nodeEditor/src/components/log/log.scss

@@ -0,0 +1,15 @@
+#log-console {
+    background: #333333;
+    height: 100px;
+    margin: 0;
+    padding: 10px;
+    width: calc(100% - 20px); 
+    overflow: hidden;
+    overflow-y: auto;
+
+    .log {
+        color: white;
+        font-size: 14px;
+        font-family: 'Courier New', Courier, monospace;
+    }
+}

+ 53 - 0
nodeEditor/src/components/log/logComponent.tsx

@@ -0,0 +1,53 @@
+
+import * as React from "react";
+import { GlobalState } from '../../globalState';
+import * as ReactDOM from 'react-dom';
+
+require("./log.scss");
+
+interface ILogComponentProps {
+    globalState: GlobalState;
+}
+
+export class LogComponent extends React.Component<ILogComponentProps, { logs: string[] }> {
+
+    constructor(props: ILogComponentProps) {
+        super(props);
+
+        this.state = { logs: [] };
+    }
+
+    componentWillMount() {
+        this.props.globalState.onLogRequiredObservable.add(log => {
+            let currentLogs = this.state.logs;
+            currentLogs.push(...log.split("\r\n"));
+
+            this.setState({ logs: currentLogs });
+        });
+    }
+
+    componentDidUpdate() {
+        const logConsole = ReactDOM.findDOMNode(this.refs["log-console"]) as HTMLElement;
+        if (!logConsole) {
+            return;
+        }
+
+        logConsole.scrollTop = logConsole.scrollHeight;
+    }
+
+    render() {
+        return (
+            <div id="log-console" ref={"log-console"} >
+                {
+                    this.state.logs.map((l, i) => {
+                        return (
+                            <div key={i} className="log">
+                                {l}
+                            </div>
+                        )
+                    })
+                }
+            </div>
+        );
+    }
+}

+ 23 - 0
nodeEditor/src/components/propertyTab/propertyTab.scss

@@ -1,6 +1,7 @@
 #propertyTab {
     $line-padding-left: 5px;
     color:white;
+    background: #333333;
 
       #header {
         height: 30px;
@@ -35,6 +36,28 @@
         }
     }
 
+    .textInputLine {
+        padding-left: $line-padding-left;
+        height: 30px;
+        display: grid;
+        grid-template-columns: 1fr 120px;
+
+        .label {
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .value {                        
+            display: flex;
+            align-items: center;
+            grid-column: 2;
+            
+            input {
+                width: 110px;
+            }
+        }
+    }
     
     .paneContainer {
         margin-top: 3px;

+ 1 - 0
nodeEditor/src/globalState.ts

@@ -12,4 +12,5 @@ export class GlobalState {
     onResetRequiredObservable = new Observable<void>();
     onUpdateRequiredObservable = new Observable<void>();
     onZoomToFitRequiredObservable = new Observable<void>();
+    onLogRequiredObservable = new Observable<string>();
 }

+ 50 - 10
nodeEditor/src/graphEditor.tsx

@@ -24,6 +24,7 @@ import { InputNodeFactory } from './components/diagram/input/inputNodeFactory';
 import { InputNodeModel } from './components/diagram/input/inputNodeModel';
 import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/textureBlock';
 import { Vector2, Vector3, Vector4, Matrix, Color3, Color4 } from 'babylonjs/Maths/math';
+import { LogComponent } from './components/log/logComponent';
 
 require("storm-react-diagrams/dist/style.min.css");
 require("./main.scss");
@@ -91,6 +92,11 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             } else {
                 newNode = new GenericNodeModel();
             }
+
+            if (options.nodeMaterialBlock.isFinalMerger) {
+                this.props.globalState.nodeMaterial!.addOutputNode(options.nodeMaterialBlock);
+            }
+
         } else {
             newNode = new InputNodeModel();
             (newNode as InputNodeModel).connection = options.connection;
@@ -133,7 +139,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
 
         this.props.globalState.onRebuildRequiredObservable.add(() => {
             if (this.props.globalState.nodeMaterial) {
-                this.props.globalState.nodeMaterial.build(true);
+                this.buildMaterial();
             }
             this.forceUpdate();
         });
@@ -142,7 +148,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             this._rowPos = [];
             this.build();
             if (this.props.globalState.nodeMaterial) {
-                this.props.globalState.nodeMaterial.build(true);
+                this.buildMaterial();
             }
         });
 
@@ -157,25 +163,57 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         this.build();
     }
 
+    buildMaterial() {
+        if (!this.props.globalState.nodeMaterial) {
+            return;
+        }
+
+        try {
+            this.props.globalState.nodeMaterial.build(true);
+            this.props.globalState.onLogRequiredObservable.notifyObservers("Node material build successful");
+        }
+        catch (err) {
+            this.props.globalState.onLogRequiredObservable.notifyObservers(err);
+        }
+    }
+
     build() {
         // setup the diagram model
         this._model = new DiagramModel();
 
         // Listen to events
         this._model.addListener({
+            nodesUpdated: (e) => {
+                if (!e.isCreated) {
+                    // Block is deleted
+                    let targetBlock = (e.node as GenericNodeModel).block;
+
+                    if (targetBlock && targetBlock.isFinalMerger) {
+                        this.props.globalState.nodeMaterial!.removeOutputNode(targetBlock);
+                    }
+                }
+            },
             linksUpdated: (e) => {
                 if (!e.isCreated) {
                     // Link is deleted
                     this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
                     var link = DefaultPortModel.SortInputOutput(e.link.sourcePort as DefaultPortModel, e.link.targetPort as DefaultPortModel);
                     if (link) {
-                        if (link.output.connection && link.input.connection) {
-                            // Disconnect standard nodes
-                            link.output.connection.disconnectFrom(link.input.connection)
-                            link.input.syncWithNodeMaterialConnectionPoint(link.input.connection)
-                            link.output.syncWithNodeMaterialConnectionPoint(link.output.connection)
-                        } else if (link.input.connection && link.input.connection.value) {
-                            link.input.connection.value = null;
+                        if (link.input.connection) {
+                            let targetBlock = link.input.connection.ownerBlock;
+
+                            if (targetBlock.isFinalMerger) {
+                                this.props.globalState.nodeMaterial!.removeOutputNode(targetBlock);
+                            }
+
+                            if (link.output.connection) {
+                                // Disconnect standard nodes
+                                link.output.connection.disconnectFrom(link.input.connection)
+                                link.input.syncWithNodeMaterialConnectionPoint(link.input.connection)
+                                link.output.syncWithNodeMaterialConnectionPoint(link.output.connection)
+                            } else if (link.input.connection.value) {
+                                link.input.connection.value = null;
+                            }
                         }
                     }
                 }
@@ -201,7 +239,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                                 }
                             }
                             if (this.props.globalState.nodeMaterial) {
-                                this.props.globalState.nodeMaterial.build()
+                                this.buildMaterial();
                             }
                         }
                     }
@@ -291,6 +329,8 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
 
                     {/* Property tab */}
                     <PropertyTabComponent globalState={this.props.globalState} />
+
+                    <LogComponent globalState={this.props.globalState} />
                 </div>
             </Portal>
         );

+ 8 - 3
nodeEditor/src/main.scss

@@ -1,6 +1,6 @@
 #node-editor-graph-root {
     display: grid;
-    grid-template-rows: 100%;
+    grid-template-rows: calc(100% - 120px) 120px;
     grid-template-columns: 200px calc(100% - 500px) 300px;
     height: 100%;
     width: 100%;
@@ -9,7 +9,7 @@
 }
 
 #nodeList {
-    grid-row: 1;
+    grid-row: 1 / span 2;
     grid-column: 1;
 }
 
@@ -20,6 +20,11 @@
 }
 
 #propertyTab {
-    grid-row: 1;
+    grid-row: 1 / span 2;
     grid-column: 3;
+}
+
+#log-console {
+    grid-row: 2;
+    grid-column: 2;
 }

+ 80 - 0
nodeEditor/src/sharedComponents/textInputLineComponent.tsx

@@ -0,0 +1,80 @@
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "./propertyChangedEvent";
+
+interface ITextInputLineComponentProps {
+    label: string;
+    target?: any;
+    propertyName?: string;
+    value?: string;
+    onChange?: (value: string) => void;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+}
+
+export class TextInputLineComponent extends React.Component<ITextInputLineComponentProps, { value: string }> {
+    private _localChange = false;
+
+    constructor(props: ITextInputLineComponentProps) {
+        super(props);
+
+        this.state = { value: this.props.value || this.props.target[this.props.propertyName!] || "" }
+    }
+
+    shouldComponentUpdate(nextProps: ITextInputLineComponentProps, nextState: { value: string }) {
+        if (this._localChange) {
+            this._localChange = false;
+            return true;
+        }
+
+        const newValue = nextProps.value || nextProps.target[nextProps.propertyName!];
+        if (newValue !== nextState.value) {
+            nextState.value = newValue || "";
+            return true;
+        }
+        return false;
+    }
+
+    raiseOnPropertyChanged(newValue: string, previousValue: string) {
+        if (this.props.onChange) {
+            this.props.onChange(newValue);
+            return;
+        }
+
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName!,
+            value: newValue,
+            initialValue: previousValue
+        });
+    }
+
+    updateValue(value: string) {
+
+        this._localChange = true;
+        const store = this.props.value || this.props.target[this.props.propertyName!];
+        this.setState({ value: value });
+
+        this.raiseOnPropertyChanged(value, store);
+
+        if (this.props.propertyName) {
+            this.props.target[this.props.propertyName] = value;
+        }
+    }
+
+    render() {
+        return (
+            <div className="textInputLine">
+                <div className="label">
+                    {this.props.label}
+                </div>
+                <div className="value">
+                    <input value={this.state.value} onChange={evt => this.updateValue(evt.target.value)} />
+                </div>
+            </div>
+        );
+    }
+}

+ 6 - 9
src/Materials/Node/nodeMaterialBuildStateSharedData.ts

@@ -115,23 +115,20 @@ export class NodeMaterialBuildStateSharedData {
      * Emits console errors and exceptions if there is a failing check
      */
     public emitErrors() {
-        let shouldThrowError = false;
+        let errorMessage = "";
 
         if (!this.checks.emitVertex) {
-            shouldThrowError = true;
-            console.error("NodeMaterial does not have a vertex output. You need to at least add a block that generates a glPosition value.");
+            errorMessage += "NodeMaterial does not have a vertex output. You need to at least add a block that generates a glPosition value.\r\n";
         }
         if (!this.checks.emitFragment) {
-            shouldThrowError = true;
-            console.error("NodeMaterial does not have a fragment output. You need to at least add a block that generates a glFragColor value.");
+            errorMessage += "NodeMaterial does not have a fragment output. You need to at least add a block that generates a glFragColor value.\r\n";
         }
         for (var notConnectedInput of this.checks.notConnectedNonOptionalInputs) {
-            shouldThrowError = true;
-            console.error(`input ${notConnectedInput.name} from block ${notConnectedInput.ownerBlock.name}[${notConnectedInput.ownerBlock.getClassName()}] is not connected and is not optional.`);
+            errorMessage += `input ${notConnectedInput.name} from block ${notConnectedInput.ownerBlock.name}[${notConnectedInput.ownerBlock.getClassName()}] is not connected and is not optional.\r\n`;
         }
 
-        if (shouldThrowError) {
-            throw "Build of NodeMaterial failed.";
+        if (errorMessage) {
+            throw "Build of NodeMaterial failed:\r\n" + errorMessage;
         }
     }
 }