(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define(["exports", "./NodeUniform.js", "./NodeAttribute.js", "./NodeVary.js", "./NodeVar.js", "./NodeCode.js", "./NodeKeywords.js", "./constants.js", "three"], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports, require("./NodeUniform.js"), require("./NodeAttribute.js"), require("./NodeVary.js"), require("./NodeVar.js"), require("./NodeCode.js"), require("./NodeKeywords.js"), require("./constants.js"), require("three"));
  } else {
    var mod = {
      exports: {}
    };
    factory(mod.exports, global.NodeUniform, global.NodeAttribute, global.NodeVary, global.NodeVar, global.NodeCode, global.NodeKeywords, global.constants, global.three);
    global.NodeBuilder = mod.exports;
  }
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports, _NodeUniform, _NodeAttribute, _NodeVary, _NodeVar, _NodeCode, _NodeKeywords, _constants, _three) {
  "use strict";

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.default = void 0;
  _NodeUniform = _interopRequireDefault(_NodeUniform);
  _NodeAttribute = _interopRequireDefault(_NodeAttribute);
  _NodeVary = _interopRequireDefault(_NodeVary);
  _NodeVar = _interopRequireDefault(_NodeVar);
  _NodeCode = _interopRequireDefault(_NodeCode);
  _NodeKeywords = _interopRequireDefault(_NodeKeywords);

  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

  function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }

  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }

  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }

  var shaderStages = ['fragment', 'vertex'];

  var NodeBuilder = /*#__PURE__*/function () {
    function NodeBuilder(object, renderer, parser) {
      _classCallCheck(this, NodeBuilder);

      this.object = object;
      this.material = object.material;
      this.renderer = renderer;
      this.parser = parser;
      this.nodes = [];
      this.updateNodes = [];
      this.hashNodes = {};
      this.vertexShader = null;
      this.fragmentShader = null;
      this.flowNodes = {
        vertex: [],
        fragment: []
      };
      this.flowCode = {
        vertex: '',
        fragment: ''
      };
      this.uniforms = {
        vertex: [],
        fragment: [],
        index: 0
      };
      this.codes = {
        vertex: [],
        fragment: []
      };
      this.attributes = [];
      this.varys = [];
      this.vars = {
        vertex: [],
        fragment: []
      };
      this.flow = {
        code: ''
      };
      this.stack = [];
      this.context = {
        keywords: new _NodeKeywords.default(),
        material: object.material
      };
      this.nodesData = new WeakMap();
      this.flowsData = new WeakMap();
      this.shaderStage = null;
      this.node = null;
    }

    _createClass(NodeBuilder, [{
      key: "addStack",
      value: function addStack(node) {
        /*
        if ( this.stack.indexOf( node ) !== - 1 ) {
        		console.warn( 'Recursive node: ', node );
        	}
        */
        this.stack.push(node);
      }
    }, {
      key: "removeStack",
      value: function removeStack(node) {
        var lastStack = this.stack.pop();

        if (lastStack !== node) {
          throw new Error('NodeBuilder: Invalid node stack!');
        }
      }
    }, {
      key: "addNode",
      value: function addNode(node) {
        if (this.nodes.indexOf(node) === -1) {
          var updateType = node.getUpdateType(this);

          if (updateType !== _constants.NodeUpdateType.None) {
            this.updateNodes.push(node);
          }

          this.nodes.push(node);
          this.hashNodes[node.getHash(this)] = node;
        }
      }
    }, {
      key: "getMethod",
      value: function getMethod(method) {
        return method;
      }
    }, {
      key: "getNodeFromHash",
      value: function getNodeFromHash(hash) {
        return this.hashNodes[hash];
      }
    }, {
      key: "addFlow",
      value: function addFlow(shaderStage, node) {
        this.flowNodes[shaderStage].push(node);
        return node;
      }
    }, {
      key: "setContext",
      value: function setContext(context) {
        this.context = context;
      }
    }, {
      key: "getContext",
      value: function getContext() {
        return this.context;
      }
    }, {
      key: "getTexture",
      value: function
        /* textureProperty, uvSnippet, biasSnippet = null */
      getTexture() {
        console.warn('Abstract function.');
      }
    }, {
      key: "getCubeTexture",
      value: function
        /* textureProperty, uvSnippet, biasSnippet = null */
      getCubeTexture() {
        console.warn('Abstract function.');
      } // rename to generate

    }, {
      key: "getConst",
      value: function getConst(type, value) {
        if (type === 'float') return value + (value % 1 ? '' : '.0');
        if (type === 'vec2') return "".concat(this.getType('vec2'), "( ").concat(value.x, ", ").concat(value.y, " )");
        if (type === 'vec3') return "".concat(this.getType('vec3'), "( ").concat(value.x, ", ").concat(value.y, ", ").concat(value.z, " )");
        if (type === 'vec4') return "".concat(this.getType('vec4'), "( ").concat(value.x, ", ").concat(value.y, ", ").concat(value.z, ", ").concat(value.w, " )");
        if (type === 'color') return "".concat(this.getType('vec3'), "( ").concat(value.r, ", ").concat(value.g, ", ").concat(value.b, " )");
        throw new Error("NodeBuilder: Type '".concat(type, "' not found in generate constant attempt."));
      }
    }, {
      key: "getType",
      value: function getType(type) {
        return type;
      }
    }, {
      key: "generateMethod",
      value: function generateMethod(method) {
        return method;
      }
    }, {
      key: "getAttribute",
      value: function getAttribute(name, type) {
        var attributes = this.attributes; // find attribute

        var _iterator = _createForOfIteratorHelper(attributes),
            _step;

        try {
          for (_iterator.s(); !(_step = _iterator.n()).done;) {
            var _attribute = _step.value;

            if (_attribute.name === name) {
              return _attribute;
            }
          } // create a new if no exist

        } catch (err) {
          _iterator.e(err);
        } finally {
          _iterator.f();
        }

        var attribute = new _NodeAttribute.default(name, type);
        attributes.push(attribute);
        return attribute;
      }
    }, {
      key: "getPropertyName",
      value: function getPropertyName(node
      /*, shaderStage*/
      ) {
        return node.name;
      }
    }, {
      key: "isVector",
      value: function isVector(type) {
        return /vec\d/.test(type);
      }
    }, {
      key: "isMatrix",
      value: function isMatrix(type) {
        return /mat\d/.test(type);
      }
    }, {
      key: "isShaderStage",
      value: function isShaderStage(shaderStage) {
        return this.shaderStage === shaderStage;
      }
    }, {
      key: "getTextureEncodingFromMap",
      value: function getTextureEncodingFromMap(map) {
        var encoding;

        if (map && map.isTexture) {
          encoding = map.encoding;
        } else if (map && map.isWebGLRenderTarget) {
          encoding = map.texture.encoding;
        } else {
          encoding = _three.LinearEncoding;
        }

        return encoding;
      }
    }, {
      key: "getVectorType",
      value: function getVectorType(type) {
        if (type === 'color') return 'vec3';
        if (type === 'texture') return 'vec4';
        return type;
      }
    }, {
      key: "getTypeFromLength",
      value: function getTypeFromLength(type) {
        if (type === 1) return 'float';
        if (type === 2) return 'vec2';
        if (type === 3) return 'vec3';
        if (type === 4) return 'vec4';
        return 0;
      }
    }, {
      key: "getTypeLength",
      value: function getTypeLength(type) {
        var vecType = this.getVectorType(type);
        var vecNum = /vec([2-4])/.exec(vecType);
        if (vecNum !== null) return Number(vecNum[1]);
        if (vecType === 'float' || vecType === 'bool') return 1;
        return 0;
      }
    }, {
      key: "getVectorFromMatrix",
      value: function getVectorFromMatrix(type) {
        return 'vec' + type.substr(3);
      }
    }, {
      key: "getDataFromNode",
      value: function getDataFromNode(node) {
        var shaderStage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.shaderStage;
        var nodeData = this.nodesData.get(node);

        if (nodeData === undefined) {
          nodeData = {
            vertex: {},
            fragment: {}
          };
          this.nodesData.set(node, nodeData);
        }

        return shaderStage !== null ? nodeData[shaderStage] : nodeData;
      }
    }, {
      key: "getUniformFromNode",
      value: function getUniformFromNode(node, shaderStage, type) {
        var nodeData = this.getDataFromNode(node, shaderStage);
        var nodeUniform = nodeData.uniform;

        if (nodeUniform === undefined) {
          var index = this.uniforms.index++;
          nodeUniform = new _NodeUniform.default('nodeUniform' + index, type, node);
          this.uniforms[shaderStage].push(nodeUniform);
          nodeData.uniform = nodeUniform;
        }

        return nodeUniform;
      }
    }, {
      key: "getVarFromNode",
      value: function getVarFromNode(node, type) {
        var shaderStage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.shaderStage;
        var nodeData = this.getDataFromNode(node, shaderStage);
        var nodeVar = nodeData.variable;

        if (nodeVar === undefined) {
          var vars = this.vars[shaderStage];
          var index = vars.length;
          nodeVar = new _NodeVar.default('nodeVar' + index, type);
          vars.push(nodeVar);
          nodeData.variable = nodeVar;
        }

        return nodeVar;
      }
    }, {
      key: "getVaryFromNode",
      value: function getVaryFromNode(node, type) {
        var nodeData = this.getDataFromNode(node, null);
        var nodeVary = nodeData.vary;

        if (nodeVary === undefined) {
          var varys = this.varys;
          var index = varys.length;
          nodeVary = new _NodeVary.default('nodeVary' + index, type);
          varys.push(nodeVary);
          nodeData.vary = nodeVary;
        }

        return nodeVary;
      }
    }, {
      key: "getCodeFromNode",
      value: function getCodeFromNode(node, type) {
        var shaderStage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.shaderStage;
        var nodeData = this.getDataFromNode(node);
        var nodeCode = nodeData.code;

        if (nodeCode === undefined) {
          var codes = this.codes[shaderStage];
          var index = codes.length;
          nodeCode = new _NodeCode.default('nodeCode' + index, type);
          codes.push(nodeCode);
          nodeData.code = nodeCode;
        }

        return nodeCode;
      }
    }, {
      key: "addFlowCode",
      value: function addFlowCode(code) {
        this.flow.code += code;
      }
    }, {
      key: "getFlowData",
      value: function getFlowData(shaderStage, node) {
        return this.flowsData.get(node);
      }
    }, {
      key: "flowNode",
      value: function flowNode(node) {
        this.node = node;
        var output = node.getNodeType(this);
        var flowData = this.flowChildNode(node, output);
        this.flowsData.set(node, flowData);
        this.node = null;
        return flowData;
      }
    }, {
      key: "flowChildNode",
      value: function flowChildNode(node) {
        var output = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
        var previousFlow = this.flow;
        var flow = {
          code: ''
        };
        this.flow = flow;
        flow.result = node.build(this, output);
        this.flow = previousFlow;
        return flow;
      }
    }, {
      key: "flowNodeFromShaderStage",
      value: function flowNodeFromShaderStage(shaderStage, node) {
        var output = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
        var propertyName = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
        var previousShaderStage = this.shaderStage;
        this.setShaderStage(shaderStage);
        var flowData = this.flowChildNode(node, output);

        if (propertyName !== null) {
          flowData.code += "".concat(propertyName, " = ").concat(flowData.result, ";\n\t");
        }

        this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code;
        this.setShaderStage(previousShaderStage);
        return flowData;
      }
    }, {
      key: "getAttributes",
      value: function getAttributes(shaderStage) {
        var snippet = '';

        if (shaderStage === 'vertex') {
          var attributes = this.attributes;

          for (var index = 0; index < attributes.length; index++) {
            var attribute = attributes[index];
            snippet += "layout(location = ".concat(index, ") in ").concat(attribute.type, " ").concat(attribute.name, "; ");
          }
        }

        return snippet;
      }
    }, {
      key: "getVarys",
      value: function
        /*shaderStage*/
      getVarys() {
        console.warn('Abstract function.');
      }
    }, {
      key: "getVars",
      value: function getVars(shaderStage) {
        var snippet = '';
        var vars = this.vars[shaderStage];

        for (var index = 0; index < vars.length; index++) {
          var variable = vars[index];
          snippet += "".concat(variable.type, " ").concat(variable.name, "; ");
        }

        return snippet;
      }
    }, {
      key: "getUniforms",
      value: function
        /*shaderStage*/
      getUniforms() {
        console.warn('Abstract function.');
      }
    }, {
      key: "getCodes",
      value: function getCodes(shaderStage) {
        var codes = this.codes[shaderStage];
        var code = '';

        var _iterator2 = _createForOfIteratorHelper(codes),
            _step2;

        try {
          for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
            var nodeCode = _step2.value;
            code += nodeCode.code + '\n';
          }
        } catch (err) {
          _iterator2.e(err);
        } finally {
          _iterator2.f();
        }

        return code;
      }
    }, {
      key: "getHash",
      value: function getHash() {
        return this.vertexShader + this.fragmentShader;
      }
    }, {
      key: "getShaderStage",
      value: function getShaderStage() {
        return this.shaderStage;
      }
    }, {
      key: "setShaderStage",
      value: function setShaderStage(shaderStage) {
        this.shaderStage = shaderStage;
      }
    }, {
      key: "buildCode",
      value: function buildCode() {
        console.warn('Abstract function.');
      }
    }, {
      key: "build",
      value: function build() {
        if (this.context.vertex && this.context.vertex.isNode) {
          this.flowNodeFromShaderStage('vertex', this.context.vertex);
        }

        var _iterator3 = _createForOfIteratorHelper(shaderStages),
            _step3;

        try {
          for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
            var shaderStage = _step3.value;
            this.setShaderStage(shaderStage);
            var flowNodes = this.flowNodes[shaderStage];

            var _iterator4 = _createForOfIteratorHelper(flowNodes),
                _step4;

            try {
              for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
                var node = _step4.value;
                this.flowNode(node, shaderStage);
              }
            } catch (err) {
              _iterator4.e(err);
            } finally {
              _iterator4.f();
            }
          }
        } catch (err) {
          _iterator3.e(err);
        } finally {
          _iterator3.f();
        }

        this.setShaderStage(null);
        this.buildCode();
        return this;
      }
    }, {
      key: "format",
      value: function format(snippet, fromType, toType) {
        fromType = this.getVectorType(fromType);
        toType = this.getVectorType(toType);
        var typeToType = "".concat(fromType, " to ").concat(toType);

        switch (typeToType) {
          case 'int to float':
            return "".concat(this.getType('float'), "( ").concat(snippet, " )");

          case 'int to vec2':
            return "".concat(this.getType('vec2'), "( ").concat(this.getType('float'), "( ").concat(snippet, " ) )");

          case 'int to vec3':
            return "".concat(this.getType('vec3'), "( ").concat(this.getType('float'), "( ").concat(snippet, " ) )");

          case 'int to vec4':
            return "".concat(this.getType('vec4'), "( ").concat(this.getType('vec3'), "( ").concat(this.getType('float'), "( ").concat(snippet, " ) ), 1.0 )");

          case 'float to int':
            return "".concat(this.getType('int'), "( ").concat(snippet, " )");

          case 'float to vec2':
            return "".concat(this.getType('vec2'), "( ").concat(snippet, " )");

          case 'float to vec3':
            return "".concat(this.getType('vec3'), "( ").concat(snippet, " )");

          case 'float to vec4':
            return "".concat(this.getType('vec4'), "( ").concat(this.getType('vec3'), "( ").concat(snippet, " ), 1.0 )");

          case 'vec2 to int':
            return "".concat(this.getType('int'), "( ").concat(snippet, ".x )");

          case 'vec2 to float':
            return "".concat(snippet, ".x");

          case 'vec2 to vec3':
            return "".concat(this.getType('vec3'), "( ").concat(snippet, ", 0.0 )");

          case 'vec2 to vec4':
            return "".concat(this.getType('vec4'), "( ").concat(snippet, ".xy, 0.0, 1.0 )");

          case 'vec3 to int':
            return "".concat(this.getType('int'), "( ").concat(snippet, ".x )");

          case 'vec3 to float':
            return "".concat(snippet, ".x");

          case 'vec3 to vec2':
            return "".concat(snippet, ".xy");

          case 'vec3 to vec4':
            return "".concat(this.getType('vec4'), "( ").concat(snippet, ", 1.0 )");

          case 'vec4 to int':
            return "".concat(this.getType('int'), "( ").concat(snippet, ".x )");

          case 'vec4 to float':
            return "".concat(snippet, ".x");

          case 'vec4 to vec2':
            return "".concat(snippet, ".xy");

          case 'vec4 to vec3':
            return "".concat(snippet, ".xyz");

          case 'mat3 to int':
            return "".concat(this.getType('int'), "( ").concat(snippet, " * ").concat(this.getType('vec3'), "( 1.0 ) ).x");

          case 'mat3 to float':
            return "( ".concat(snippet, " * ").concat(this.getType('vec3'), "( 1.0 ) ).x");

          case 'mat3 to vec2':
            return "( ".concat(snippet, " * ").concat(this.getType('vec3'), "( 1.0 ) ).xy");

          case 'mat3 to vec3':
            return "( ".concat(snippet, " * ").concat(this.getType('vec3'), "( 1.0 ) ).xyz");

          case 'mat3 to vec4':
            return "".concat(this.getType('vec4'), "( ").concat(snippet, " * ").concat(this.getType('vec3'), "( 1.0 ), 1.0 )");

          case 'mat4 to int':
            return "".concat(this.getType('int'), "( ").concat(snippet, " * ").concat(this.getType('vec4'), "( 1.0 ) ).x");

          case 'mat4 to float':
            return "( ".concat(snippet, " * ").concat(this.getType('vec4'), "( 1.0 ) ).x");

          case 'mat4 to vec2':
            return "( ".concat(snippet, " * ").concat(this.getType('vec4'), "( 1.0 ) ).xy");

          case 'mat4 to vec3':
            return "( ".concat(snippet, " * ").concat(this.getType('vec4'), "( 1.0 ) ).xyz");

          case 'mat4 to vec4':
            return "( ".concat(snippet, " * ").concat(this.getType('vec4'), "( 1.0 ) )");
        }

        return snippet;
      }
    }, {
      key: "getSignature",
      value: function getSignature() {
        return "// Three.js r".concat(_three.REVISION, " \u2022 NodeMaterial System\n");
      }
    }]);

    return NodeBuilder;
  }();

  var _default = NodeBuilder;
  _exports.default = _default;
});