Home Reference Source

cables_dev/cables/src/libs/cgl/shadergraph/cgl_shadergraphop.js

class ShaderGraphOp
{
    constructor(op, src)
    {
        op.sgOp = this;
        this._op = op;
        this._inPorts = [];
        this._outPorts = [];
        this._defines = [];
        this.enabled = true;
        this.info = null;

        if (src)
            this.parseCode(src);

        this._op.on("onLinkChanged", this.updateGraph.bind(this));
        this.addPortWatcher();
    }

    addPortWatcher()
    {
        for (let i = 0; i < this._op.portsIn.length; i++)
        {
            if (this._op.portsIn[i].type != CABLES.OP_PORT_TYPE_OBJECT) continue;


            if (this._op.portsIn[i].uiAttribs.objType && this._op.portsIn[i].uiAttribs.objType.indexOf("sg_") == 0) this._op.portsIn[i].setUiAttribs({ "display": "sg_vec" });

            this._op.portsIn[i].on("change", this.updateGraph.bind(this));
        }
    }

    updateGraph()
    {
        for (let i = 0; i < this._op.portsOut.length; i++)
        {
            if (this._op.portsOut[i].type != CABLES.OP_PORT_TYPE_OBJECT) continue;
            // this._op.portsOut[i].setRef(null);
            this._op.portsOut[i].setRef({});
        }
    }

    isTypeDef(str)
    {
        return str == "void" || str == "float" || str == "sampler2D" || str == "vec2" || str == "vec3" || str == "vec4" || str == "void" || str == "mat4" || str == "mat3" || str == "mat2" || str == "out";
    }

    parseCode(_code)
    {
        let code = _code;
        let info = { "functions": [], "uniforms": [] };

        const origLines = code.split("\n");
        const prelines = code.split("\n");

        for (let i = 0; i < prelines.length; i++)
            prelines[i] += "###line:" + i + ":###";

        code = prelines.join("\n");

        code = code.replaceAll("{{", ""); // remove spaces before brackets
        code = code.replaceAll("}}", ""); // remove spaces before brackets

        // code = code.replaceAll(/\/\*[\s\S]*?\*\/|\/\/.*/g, ""); // remove comments
        // code = code.replaceAll(/{[^{}]*}/g, "{}"); // remove function content
        code = code.replaceAll("\n{", "{");
        code = code.replaceAll(";", " ;"); // add spaces for better splitting
        // code = code.replaceAll("{", "{"); // remove spaces before brackets
        code = code.replaceAll("(", " ( "); // add spaces for better splitting
        code = code.replaceAll(")", " ) "); // add spaces for better splitting
        code = code.replaceAll(",", " , "); // add spaces for better splitting
        code = code.replace(/ +(?= )/g, ""); // remove double whitespaces

        // console.log(origLines);

        const lines = code.split("\n");

        // console.log(lines);

        for (let i = 0; i < lines.length; i++)
        {
            // identify function
            if (lines[i].indexOf("{") > 0 && lines[i].indexOf("(") > 0 && lines[i].indexOf(")") > 0)
            {
                const words = lines[i].split(" ");

                if (this.isTypeDef(words[0])) // function header start with return typedef
                {
                    // merge all the remaining lines to be able to search for the end of the function ...
                    let remainingcode = "";
                    for (let j = i; j < lines.length; j++) remainingcode += lines[j];

                    // search for all {} and find the end of the function body...
                    const startPos = remainingcode.indexOf("{");
                    let count = 0;
                    let cc = 0;
                    for (cc = startPos; cc < remainingcode.length; cc++)
                    {
                        if (remainingcode.charAt(cc) == "{") count++;
                        if (remainingcode.charAt(cc) == "}") count--;
                        if (count == 0) break;
                    }

                    // console.log("remainingcode", remainingcode);
                    // parse the first and last line numbers
                    let functioncode = remainingcode.substring(0, cc + 1);
                    const linenums = functioncode.split("###line:");

                    // console.log("functioncode", functioncode);
                    // console.log("linenums", linenums);

                    let lineNumStart = i, lineNumEnd = i - 1;
                    if (linenums.length > 1)
                    {
                        lineNumStart = parseInt(linenums[1].split(":")[0]);
                        lineNumEnd = parseInt(linenums[linenums.length - 1].split(":")[0]);
                    }

                    functioncode = "";

                    // concat the whole function
                    for (let j = lineNumStart; j <= lineNumEnd + 1; j++)
                        if (origLines[j])functioncode += origLines[j] + "\n";

                    const infoFunc = { "name": words[1], "type": words[0], "params": [], "src": functioncode };
                    infoFunc.uniqueName = words[0] + "_" + words[1];

                    // analyze function head and read all parameters
                    words.length = words.indexOf(")") + 1;
                    for (let j = 3; j < words.length - 2; j += 3)
                    {
                        infoFunc.params.push({ "name": words[j + 1], "type": words[j] });
                        infoFunc.uniqueName += "_" + words[j + 0] + "_" + words[j + 1];
                    }

                    info.functions.push(infoFunc);
                }
            }

            if (lines[i].indexOf("UNI") == 0 || lines[i].indexOf("uniform") == 0)
            {
                const words = lines[i].split(" ");
                if (this.isTypeDef(words[1])) info.uniforms.push({ "name": words[2], "type": words[1] });
            }
        }

        info.src = _code;
        // if (this._op.uiAttribs.comment)_code = "//" + this._op.uiAttribs.comment + "\n" + _code;

        this.info = info;
        this.updatePorts(this.info);

        return info;
    }

    updatePorts(info)
    {
        const foundPortInNames = {};
        this._op.shaderSrc = info.src;

        if (info.functions.length > 0)
        {
            const f = info.functions[info.functions.length - 1];
            this._op.setTitle(f.name);
            this._op.shaderFunc = f.name;

            for (let p = 0; p < f.params.length; p++)
            {
                const port = this._op.getPort(f.params[p].name) || this._op.inObject(f.params[p].name);

                // let changed = false;
                // if (port.uiAttribs.objType != f.params[p].type) changed = true;
                port.setUiAttribs({ "objType": "sg_" + f.params[p].type, "ignoreObjTypeErrors": true });
                // if (changed) port.setRef(port.get());

                this._inPorts.push(port);

                foundPortInNames[f.params[p].name] = true;
            }

            let port = this._op.getPort("Result");
            if (!port)
            {
                port = this._op.outObject("Result");
                this._outPorts.push(port);
            }

            // let changed = false;
            // if (port.uiAttribs.objType != f.type) changed = true;
            port.setUiAttribs({ "objType": "sg_" + f.type });
            // if (changed) port.setRef(port.get());
        }

        for (let i = 0; i < this._inPorts.length; i++) if (!foundPortInNames[this._inPorts[i].name]) this._inPorts[i].remove();

        this.addPortWatcher();
        this._op.refreshParams();
    }


    /**
 * add a define to a shader, e.g.  #define DO_THIS_THAT 1
 * @function define
 * @memberof Shader
 * @instance
 * @param {String} name
 * @param {Any} value (can be empty)
 */
    define(name, value)
    {
        if (value === null || value === undefined) value = "";

        if (typeof (value) == "object") // port
        {
            value.removeEventListener("change", value.onDefineChange);
            value.onDefineChange = (v) =>
            {
                this.define(name, v);
            };
            value.on("change", value.onDefineChange);

            value = value.get();
        }


        for (let i = 0; i < this._defines.length; i++)
        {
            if (this._defines[i][0] == name && this._defines[i][1] == value) return;
            if (this._defines[i][0] == name)
            {
                this._defines[i][1] = value;
                // this.setWhyCompile("define " + name + " " + value);

                // this._needsRecompile = true;
                return;
            }
        }
        // this.setWhyCompile("define " + name + " " + value);
        // this._needsRecompile = true;
        this._defines.push([name, value]);
        this.updateGraph();
    }

    getDefines()
    {
        return this._defines;
    }

    getDefine(name)
    {
        for (let i = 0; i < this._defines.length; i++)
            if (this._defines[i][0] == name) return this._defines[i][1];
        return null;
    }

    /**
  * return true if shader has define
  * @function hasDefine
  * @memberof Shader
  * @instance
  * @param {String} name
  * @return {Boolean}
  */
    hasDefine(name)
    {
        for (let i = 0; i < this._defines.length; i++)
            if (this._defines[i][0] == name) return true;
    }

    /**
  * remove a define from a shader
  * @param {name} name
  * @function removeDefine
  * @memberof Shader
  * @instance
  */
    removeDefine(name)
    {
        for (let i = 0; i < this._defines.length; i++)
        {
            if (this._defines[i][0] == name)
            {
                this._defines.splice(i, 1);
                // this._needsRecompile = true;

                // this.setWhyCompile("define removed:" + name);
                this.updateGraph();
                return;
            }
        }
    }

    toggleDefine(name, enabled)
    {
        if (enabled) this.define(name);
        else this.removeDefine(name);
        this.updateGraph();
    }
}


ShaderGraphOp.getMaxGenTypeFromPorts = (ports, portsSetType) =>
{
    const types = ["sg_float", "sg_vec2", "sg_vec3", "sg_vec4"];
    let typeIdx = 0;

    for (let j = 0; j < ports.length; j++)
        for (let i = 0; i < ports[j].links.length; i++)
        {
            const t = types.indexOf(ports[j].links[i].getOtherPort(ports[j]).uiAttribs.objType);
            typeIdx = Math.max(typeIdx, t);
        }

    const t = types[typeIdx];

    if (portsSetType)
        for (let i = 0; i < portsSetType.length; i++)
            portsSetType[i].setUiAttribs({ "objType": t });

    return t;
};

export { ShaderGraphOp };