Przeglądaj źródła

Allow parenthesis usage in #if expressions in shader code

Popov72 5 lat temu
rodzic
commit
aa7996960d

+ 1 - 0
dist/preview release/what's new.md

@@ -57,6 +57,7 @@
 - Added to `FresnelParameters` constructor options and equals method ([brianzinn](https://github.com/brianzinn))
 - Added `AddAttribute` to `CustomMaterial` and `PBRCustomMaterial` ([Popov72](https://github.com/Popov72))
 - `setTexture` and `setTextureArray` from `ShaderMaterial` take now a `BaseTexture` as input instead of a `Texture`, allowing to pass a `CubeTexture` ([Popov72](https://github.com/Popov72))
+- Allow parenthesis usage in `#if` expressions in shader code ([Popov72](https://github.com/Popov72))
 
 ### WebXR
 

+ 92 - 0
src/Engines/Processors/Expressions/shaderDefineExpression.ts

@@ -3,4 +3,96 @@ export class ShaderDefineExpression {
     public isTrue(preprocessors: { [key: string]: string }): boolean {
         return true;
     }
+
+    private static OperatorPriority: { [name: string]: number } = {
+        ")": 0,
+        "(": 1,
+        "||": 2,
+        "&&": 3,
+    };
+
+    private static Stack = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
+
+    public static postfixToInfix(postfix: string[]): string {
+        const stack: string[] = [];
+
+        for (let c of postfix) {
+            if (ShaderDefineExpression.OperatorPriority[c] === undefined) {
+                stack.push(c);
+            } else {
+                const v1 = stack[stack.length - 1],
+                      v2 = stack[stack.length - 2];
+
+                stack.length -= 2;
+                stack.push(`(${v2}${c}${v1})`);
+            }
+        }
+
+        return stack[stack.length - 1];
+    }
+
+    public static infixToPostfix(infix: string): string[] {
+        const result: string[] = [];
+
+        let stackIdx = -1;
+
+        const pushOperand = () => {
+            operand = operand.trim();
+            if (operand !== '') {
+                result.push(operand);
+                operand = '';
+            }
+        };
+
+        const push = (s: string) => {
+            if (stackIdx < ShaderDefineExpression.Stack.length - 1) {
+                ShaderDefineExpression.Stack[++stackIdx] = s;
+            }
+        };
+
+        const peek = () => ShaderDefineExpression.Stack[stackIdx];
+
+        const pop = () => stackIdx === -1 ? '!!INVALID EXPRESSION!!' : ShaderDefineExpression.Stack[stackIdx--];
+
+        let idx = 0,
+            operand = '';
+
+        while (idx < infix.length) {
+            const c = infix.charAt(idx),
+                  token = idx < infix.length - 1 ? infix.substr(idx, 2) : '';
+
+            if (c === '(') {
+                operand = '';
+                push(c);
+            } else if (c === ')') {
+                pushOperand();
+                while (stackIdx !== -1 && peek() !== '(') {
+                    result.push(pop());
+                }
+                pop();
+            } else if (ShaderDefineExpression.OperatorPriority[token] > 1) {
+                pushOperand();
+                while (stackIdx !== -1 && ShaderDefineExpression.OperatorPriority[peek()] >= ShaderDefineExpression.OperatorPriority[token]) {
+                    result.push(pop());
+                }
+                push(token);
+                idx++;
+            } else {
+                operand += c;
+            }
+            idx++;
+        }
+
+        pushOperand();
+
+        while (stackIdx !== -1) {
+            if (peek() === '(') {
+                pop();
+            } else {
+                result.push(pop());
+            }
+        }
+
+        return result;
+    }
 }

+ 40 - 21
src/Engines/Processors/shaderProcessor.ts

@@ -15,6 +15,9 @@ declare type LoadFileError = import("../../Misc/fileTools").LoadFileError;
 declare type IOfflineProvider = import("../../Offline/IOfflineProvider").IOfflineProvider;
 declare type IFileRequest  = import("../../Misc/fileRequest").IFileRequest;
 
+const regexSE = /defined\s*?\((.+?)\)/g;
+const regexSERevert = /defined\s*?\[(.+?)\]/g;
+
 /** @hidden */
 export class ShaderProcessor {
     public static Process(sourceCode: string, options: ProcessingOptions, callback: (migratedCode: string) => void) {
@@ -74,31 +77,47 @@ export class ShaderProcessor {
     }
 
     private static _BuildSubExpression(expression: string): ShaderDefineExpression {
-        let indexOr = expression.indexOf("||");
-        if (indexOr === -1) {
-            let indexAnd = expression.indexOf("&&");
-            if (indexAnd > -1) {
-                let andOperator = new ShaderDefineAndOperator();
-                let leftPart = expression.substring(0, indexAnd).trim();
-                let rightPart = expression.substring(indexAnd + 2).trim();
-
-                andOperator.leftOperand = this._BuildSubExpression(leftPart);
-                andOperator.rightOperand = this._BuildSubExpression(rightPart);
-
-                return andOperator;
-            } else {
-                return this._ExtractOperation(expression);
+        expression = expression.replace(regexSE, "defined[$1]");
+
+        const postfix = ShaderDefineExpression.infixToPostfix(expression);
+
+        const stack: (string | ShaderDefineExpression)[] = [];
+
+        for (let c of postfix) {
+            if (c !== '||' && c !== '&&') {
+                stack.push(c);
+            } else if (stack.length >= 2) {
+                let v1 = stack[stack.length - 1],
+                    v2 = stack[stack.length - 2];
+
+                stack.length -= 2;
+
+                let operator = c == '&&' ? new ShaderDefineAndOperator() : new ShaderDefineOrOperator();
+
+                if (typeof(v1) === 'string') {
+                    v1 = v1.replace(regexSERevert, "defined($1)");
+                }
+
+                if (typeof(v2) === 'string') {
+                    v2 = v2.replace(regexSERevert, "defined($1)");
+                }
+
+                operator.leftOperand = typeof(v2) === 'string' ? this._ExtractOperation(v2) : v2;
+                operator.rightOperand = typeof(v1) === 'string' ? this._ExtractOperation(v1) : v1;
+
+                stack.push(operator);
             }
-        } else {
-            let orOperator = new ShaderDefineOrOperator();
-            let leftPart = expression.substring(0, indexOr).trim();
-            let rightPart = expression.substring(indexOr + 2).trim();
+        }
 
-            orOperator.leftOperand = this._BuildSubExpression(leftPart);
-            orOperator.rightOperand = this._BuildSubExpression(rightPart);
+        let result = stack[stack.length - 1];
 
-            return orOperator;
+        if (typeof(result) === 'string') {
+            result = result.replace(regexSERevert, "defined($1)");
         }
+
+        // note: stack.length !== 1 if there was an error in the parsing
+
+        return typeof(result) === 'string' ? this._ExtractOperation(result) : result;
     }
 
     private static _BuildExpression(line: string, start: number): ShaderCodeTestNode {