import { PBShaderExp } from '@zephyr3d/device';
import { ConstantScalarNode, ConstantVec2Node, ConstantVec3Node, ConstantVec4Node, ConstantBooleanNode, ConstantBVec2Node, ConstantBVec3Node, ConstantBVec4Node } from '../common/constants.js';
import { ASSERT } from '@zephyr3d/base';
import { BaseTextureNode, TextureSampleNode, PannerNode } from './texture.js';
import { MakeVectorNode, SwizzleNode, CompComparisonNode, AnyConditionNode, AllConditionNode, SelectionNode, Hash1Node, Hash2Node, Hash3Node, SimplexNoise2DNode, PerlinNoise2DNode, GenericMathNode, TransformNode, RotateAboutAxisNode } from '../common/math.js';
import { ResolveVertexPositionNode, ResolveVertexNormalNode, ResolveVertexTangentNode, VertexColorNode, VertexUVNode, VertexPositionNode, PixelWorldPositionNode, VertexNormalNode, PixelNormalNode, VertexTangentNode, VertexBinormalNode, ViewMatrixNode, ProjectionMatrixNode, ViewProjMatrixNode, InvProjMatrixNode, InvViewProjMatrixNode, BillboardMatrixNode, CameraPositionNode, CameraVectorNode, CameraNearFarNode, SkyEnvTextureNode, ElapsedTimeNode } from './inputs.js';
import { ShaderHelper } from '../../../material/shader/helper.js';
import { FunctionCallNode, FunctionInputNode, FunctionOutputNode } from './func.js';
import { hash11, hash21, hash31, hash12, hash22, hash32, hash13, hash23, hash33, valueNoise } from '../../../shaders/noise.js';
import { VertexBlockNode } from './pbr.js';

/**
 * Abstract base class for intermediate representation expressions
 *
 * @remarks
 * Represents a node in the IR tree that can be compiled to shader code.
 * Uses reference counting to determine if intermediate variables are needed.
 * Each expression can be referenced multiple times in the final shader code.
 *
 * The IR expression tree is built during compilation and then translated
 * to actual shader code via the `create()` method.
 *
 * @public
 */ class IRExpression {
    /** Reference count for this expression (number of times it's used) */ _ref;
    /** Cached output expressions for each output slot */ _outputs;
    /**
   * Creates a new IR expression
   *
   * @remarks
   * Initializes reference count to 0 and empty outputs array.
   */ constructor(){
        this._ref = 0;
        this._outputs = [];
    }
    /** Gets the array of output expressions */ get outputs() {
        return this._outputs;
    }
    /**
   * Increments the reference count
   *
   * @returns This expression for method chaining
   *
   * @remarks
   * Called when this expression is referenced by another expression.
   * Reference count is used to determine if a temporary variable is needed.
   */ addRef() {
        this._ref++;
        return this;
    }
    /**
   * Generates a unique temporary variable name
   *
   * @param scope - The shader scope to check for name conflicts
   * @returns A unique temporary variable name (e.g., 'tmp0', 'tmp1')
   *
   * @remarks
   * Ensures the generated name doesn't conflict with existing variables in the scope.
   */ getTmpName(scope) {
        let tmp = 0;
        for(;;){
            const name = `tmp${tmp++}`;
            if (!scope[name]) {
                return name;
            }
        }
    }
    /**
   * Converts this expression to a uniform value if applicable
   *
   * @param _node - The graph node associated with this expression
   * @returns The uniform value descriptor, or null if not a uniform
   *
   * @remarks
   * Only constant expressions can be converted to uniforms.
   * Returns null by default; overridden by constant expression types.
   */ asUniformValue(_node) {
        return null;
    }
    /**
   * Converts this expression to a uniform texture if applicable
   *
   * @param _node - The graph node associated with this expression
   * @returns The uniform texture descriptor, or null if not a texture uniform
   *
   * @remarks
   * Only texture constant expressions can be converted to texture uniforms.
   * Returns null by default; overridden by texture constant expression types.
   */ asUniformTexture(_node) {
        return null;
    }
    /**
   * Reset state for creation
   */ reset() {}
}
/**
 * IR expression for a constant scalar (float) value
 *
 * @public
 */ class IRConstantf extends IRExpression {
    /** The constant float value */ value;
    /** The uniform parameter name, or empty string for literals */ name;
    /**
   * Creates a constant float expression
   *
   * @param value - The float value
   * @param paramName - The uniform parameter name, or empty string for literals
   */ constructor(value, paramName){
        super();
        this.value = value;
        this.name = paramName;
    }
    /**
   * Generates shader code for this constant
   *
   * @param pb - The program builder
   * @returns A uniform reference or literal number
   *
   * @remarks
   * If a parameter name exists, creates a uniform in the global scope.
   * Otherwise, returns the literal value.
   */ create(pb) {
        if (this.name) {
            if (!pb.getGlobalScope()[this.name]) {
                pb.getGlobalScope()[this.name] = pb.float().uniform(2);
            }
            return pb.getGlobalScope()[this.name];
        }
        return this.value;
    }
    /**
   * Converts to a uniform value descriptor
   *
   * @param node - The graph node
   * @returns Uniform value descriptor if this is a uniform parameter, null otherwise
   */ asUniformValue() {
        return this.name ? {
            name: this.name,
            value: [
                this.value
            ],
            type: 'float'
        } : null;
    }
}
/**
 * IR expression for a constant scalar (boolean) value
 *
 * @public
 */ class IRConstantb extends IRExpression {
    /** The constant boolean value */ value;
    /**
   * Creates a constant boolean expression
   *
   * @param value - The boolean value
   */ constructor(value){
        super();
        this.value = value;
    }
    /**
   * Generates shader code for this constant
   *
   * @returns The literal boolean value
   *
   */ create() {
        return this.value;
    }
}
/**
 * IR expression for a constant vector value
 *
 * @public
 */ class IRConstantfv extends IRExpression {
    /** The constant vector value as an array of floats */ value;
    /** The uniform parameter name, or empty string for literals */ name;
    /** vector type */ type;
    /**
   * Creates a constant vector expression
   *
   * @param value - The vector value as an array (length 2-4)
   * @param paramName - The uniform parameter name, or empty string for literals
   */ constructor(value, paramName, type){
        super();
        this.value = value;
        this.name = paramName;
        this.type = type;
    }
    /**
   * Generates shader code for this constant vector
   *
   * @param pb - The program builder
   * @returns A uniform reference or vector constructor expression
   *
   * @remarks
   * If a parameter name exists, creates a uniform in the global scope.
   * The vector size is determined by the array length (vec2/vec3/vec4).
   */ create(pb) {
        if (this.name) {
            if (!pb.getGlobalScope()[this.name]) {
                // @ts-ignore
                pb.getGlobalScope()[this.name] = pb[`vec${this.value.length}`]().uniform(2);
            }
            return pb.getGlobalScope()[this.name];
        }
        return(// @ts-ignore
        Array.isArray(this.value) ? pb[`vec${this.value.length}`](...this.value) : this.value);
    }
    /**
   * Converts to a uniform value descriptor
   *
   * @param node - The graph node
   * @returns Uniform value descriptor if this is a uniform parameter, null otherwise
   */ asUniformValue() {
        return this.name ? {
            name: this.name,
            type: this.type,
            value: this.value
        } : null;
    }
}
/**
 * IR expression for a constant boolean vector value
 *
 * @public
 */ class IRConstantbv extends IRExpression {
    /** The constant vector value as an array of booleans */ value;
    /**
   * Creates a constant boolean vector expression
   *
   * @param value - The vector value as an array (length 2-4)
   */ constructor(value){
        super();
        this.value = value;
    }
    /**
   * Generates shader code for this constant vector
   *
   * @param pb - The program builder
   *
   * @returns A boolean vector constructor expression
   */ create(pb) {
        // @ts-ignore
        return pb[`bvec${this.value.length}`](...this.value);
    }
}
/**
 * IR expression for shader input variables
 *
 * @remarks
 * Represents variables that are passed into the shader from the vertex stage
 * or defined in the current scope. Examples include vertex attributes,
 * varyings, and built-in variables.
 *
 * @example
 * ```typescript
 * // Reference to vertex color
 * const vertexColor = new IRInput('zVertexColor');
 *
 * // Reference via function
 * const worldPos = new IRInput((scope) => scope.zWorldPos);
 * ```
 *
 * @public
 */ class IRInput extends IRExpression {
    /** The variable name or a function that retrieves the variable from scope */ func;
    /**
   * Creates an input variable expression
   *
   * @param func - Variable name string or function to retrieve the variable
   */ constructor(func){
        super();
        this.func = func;
    }
    /**
   * Generates shader code for this input variable
   *
   * @param pb - The program builder
   * @returns The shader expression referencing the input variable
   */ create(pb) {
        return typeof this.func === 'string' ? pb.getCurrentScope()[this.func] : this.func(pb.getCurrentScope());
    }
}
/**
 * IR expression for function calls and operations
 *
 * @remarks
 * Represents built-in shader functions (add, mul, sin, etc.) or vector constructors.
 * Uses reference counting to determine if result should be stored in a temporary variable.
 *
 * @example
 * ```typescript
 * // Addition: a + b
 * const add = new IRFunc([exprA, exprB], 'add');
 *
 * // Vector constructor: vec3(x, y, z)
 * const vec = new IRFunc([x, y, z], 'vec3');
 * ```
 *
 * @public
 */ class IRFunc extends IRExpression {
    /** Array of parameters (can be expressions or literal numbers) */ params;
    /** The function name (e.g., 'add', 'mul', 'sin', 'vec3') */ func;
    /** Cached temporary variable name if expression is referenced multiple times */ tmpName;
    /**
   * Creates a function call expression
   *
   * @param params - Array of parameters for the function
   * @param func - The function name or function implementation
   *
   * @remarks
   * Increments reference count for all expression parameters.
   */ constructor(params, func){
        super();
        this.params = params.map((param)=>param instanceof IRExpression ? param.addRef() : param);
        this.func = func;
        this.tmpName = '';
    }
    /** Reset for next creation */ reset() {
        this.tmpName = '';
    }
    /**
   * Generates shader code for this function call
   *
   * @param pb - The program builder
   * @returns The shader expression for the function result
   *
   * @remarks
   * If referenced multiple times (_ref > 1), stores result in a temporary variable.
   * Otherwise, generates the function call inline.
   */ create(pb) {
        if (this.tmpName) {
            const exp = pb.getCurrentScope()[this.tmpName];
            if (exp) {
                return exp;
            }
            // Scope changed, recompute in current scope
            this.tmpName = '';
        }
        const params = this.params.map((param)=>param instanceof IRExpression ? param.create(pb) : param);
        // @ts-ignore
        const exp = typeof this.func === 'string' ? pb[this.func](...params) : this.func(pb, ...params);
        if (this._ref === 1) {
            return exp;
        } else {
            this.tmpName = this.getTmpName(pb.getCurrentScope());
            pb.getCurrentScope()[this.tmpName] = exp;
            return pb.getCurrentScope()[this.tmpName];
        }
    }
}
/**
 * IR expression for function output values
 *
 * @remarks
 * Represents the output of a material function (subgraph).
 * Handles caching of output values when referenced multiple times.
 *
 * @example
 * ```typescript
 * // Function returns a processed color value
 * const output = new IRFunctionOutput(colorExpression);
 * ```
 *
 * @public
 */ class IRFunctionOutput extends IRExpression {
    /** Cached temporary variable name for the output */ tmpName;
    /** The input expression to output */ input;
    /**
   * Creates a function output expression
   *
   * @param input - The expression or value to output
   */ constructor(input){
        super();
        this.input = input;
        this.tmpName = '';
    }
    /** Reset for next creation */ reset() {
        this.tmpName = '';
    }
    /**
   * Generates shader code for this function output
   *
   * @param pb - The program builder
   * @returns The shader expression or value
   *
   * @remarks
   * If referenced multiple times, stores result in a temporary variable.
   */ create(pb) {
        if (this.tmpName) {
            return pb.getCurrentScope()[this.tmpName];
        }
        const exp = this.input instanceof IRExpression ? this.input.create(pb) : this.input;
        if (this._ref === 1) {
            return exp;
        } else {
            this.tmpName = this.getTmpName(pb.getCurrentScope());
            pb.getCurrentScope()[this.tmpName] = exp;
            return pb.getCurrentScope()[this.tmpName];
        }
    }
}
/**
 * IR expression for calling user-defined material functions
 *
 * @remarks
 * Represents a call to a material function (subgraph) with parameters.
 * Generates both the function definition and the call site.
 * Uses a struct return type to return multiple outputs from the function.
 *
 * @example
 * ```typescript
 * // Call to a custom "calculateLighting" function
 * const call = new IRCallFunc(functionNode, [normalExpr, lightDirExpr]);
 * ```
 *
 * @public
 */ class IRCallFunc extends IRExpression {
    /** The function call node from the graph */ node;
    /** Array of argument expressions */ args;
    /** Cached temporary variable name if result is referenced multiple times */ tmpName;
    /**
   * Creates a function call expression
   *
   * @param node - The function call node
   * @param args - Array of argument expressions
   */ constructor(node, args){
        super();
        this.node = node;
        this.args = args;
        this.tmpName = '';
    }
    /** Reset for next creation */ reset() {
        this.tmpName = '';
    }
    /**
   * Generates shader code for the function definition and call
   *
   * @param pb - The program builder
   * @returns The shader expression for the function result
   *
   * @remarks
   * First generates the function definition (if not already defined),
   * then generates the function call with the provided arguments.
   * The function returns a struct containing all output values.
   */ create(pb) {
        if (this.tmpName) {
            return pb.getCurrentScope()[this.tmpName];
        }
        const that = this;
        const ir = this.node.IR;
        // @ts-ignore
        const params = this.node.args.map((v)=>pb[v.type](v.name));
        pb.func(this.node.name, params, function() {
            const outputs = ir.create(pb);
            const rettype = pb.defineStruct(that.node.outputs.map((output, index)=>{
                // @ts-ignore
                return pb[that.node.outs[index].type](output.swizzle);
            }));
            this.$return(rettype(...outputs.map((output)=>{
                return output.exp;
            })));
        });
        const args = this.args.map((arg)=>arg.create(pb));
        const exp = pb.getGlobalScope()[this.node.name](...args);
        if (this._ref === 1) {
            return exp;
        } else {
            this.tmpName = this.getTmpName(pb.getCurrentScope());
            pb.getCurrentScope()[this.tmpName] = exp;
            return pb.getCurrentScope()[this.tmpName];
        }
    }
}
class IRSelection extends IRExpression {
    a;
    b;
    cond;
    tmpName;
    constructor(a, b, cond){
        super();
        this.a = a.addRef();
        this.b = b.addRef();
        this.cond = cond.addRef();
        this.tmpName = '';
    }
    reset() {
        this.tmpName = '';
    }
    create(pb) {
        if (this.tmpName) {
            return pb.getCurrentScope()[this.tmpName];
        }
        const a = this.a.create(pb);
        const b = this.b.create(pb);
        const c = this.cond.create(pb);
        const exp = pb.select(a, b, typeof c === 'number' ? c !== 0 : c);
        if (this._ref === 1) {
            return exp;
        } else {
            this.tmpName = this.getTmpName(pb.getCurrentScope());
            pb.getCurrentScope()[this.tmpName] = exp;
            return pb.getCurrentScope()[this.tmpName];
        }
    }
}
class IRAny extends IRExpression {
    src;
    tmpName;
    constructor(src){
        super();
        this.src = src.addRef();
        this.tmpName = '';
    }
    reset() {
        this.tmpName = '';
    }
    create(pb) {
        if (this.tmpName) {
            return pb.getCurrentScope()[this.tmpName];
        }
        const src = this.src.create(pb);
        const exp = pb.any(src);
        if (this._ref === 1) {
            return exp;
        } else {
            this.tmpName = this.getTmpName(pb.getCurrentScope());
            pb.getCurrentScope()[this.tmpName] = exp;
            return pb.getCurrentScope()[this.tmpName];
        }
    }
}
class IRAll extends IRExpression {
    src;
    tmpName;
    constructor(src){
        super();
        this.src = src.addRef();
        this.tmpName = '';
    }
    reset() {
        this.tmpName = '';
    }
    create(pb) {
        if (this.tmpName) {
            return pb.getCurrentScope()[this.tmpName];
        }
        const src = this.src.create(pb);
        const exp = pb.all(src);
        if (this._ref === 1) {
            return exp;
        } else {
            this.tmpName = this.getTmpName(pb.getCurrentScope());
            pb.getCurrentScope()[this.tmpName] = exp;
            return pb.getCurrentScope()[this.tmpName];
        }
    }
}
class IRComparison extends IRExpression {
    a;
    b;
    mode;
    tmpName;
    constructor(a, b, mode){
        super();
        this.a = a.addRef();
        this.b = b.addRef();
        this.mode = mode;
        this.tmpName = '';
    }
    /** Reset for next creation */ reset() {
        this.tmpName = '';
    }
    create(pb) {
        if (this.tmpName) {
            return pb.getCurrentScope()[this.tmpName];
        }
        const a = this.a.create(pb);
        const b = this.b.create(pb);
        let exp;
        switch(this.mode){
            case 'eq':
                exp = pb.compEqual(a, b);
                break;
            case 'ne':
                exp = pb.compNotEqual(a, b);
                break;
            case 'lt':
                exp = pb.lessThan(a, b);
                break;
            case 'le':
                exp = pb.lessThanEqual(a, b);
                break;
            case 'gt':
                exp = pb.greaterThan(a, b);
                break;
            case 'ge':
                exp = pb.greaterThanEqual(a, b);
                break;
            default:
                throw new Error(`Invalid comparison mode: ${this.mode}`);
        }
        if (this._ref === 1) {
            return exp;
        } else {
            this.tmpName = this.getTmpName(pb.getCurrentScope());
            pb.getCurrentScope()[this.tmpName] = exp;
            return pb.getCurrentScope()[this.tmpName];
        }
    }
}
/**
 * IR expression for vector component swizzling
 *
 * @remarks
 * Extracts specific components from a vector using swizzle notation.
 * For scalar sources, constructs a vector with replicated values.
 *
 * @example
 * ```typescript
 * // Extract xyz from vec4
 * const xyz = new IRSwizzle(vec4Expr, 'xyz');
 *
 * // Extract single component
 * const x = new IRSwizzle(vec3Expr, 'x');
 *
 * // Swizzle and reorder
 * const bgr = new IRSwizzle(rgbExpr, 'bgr');
 * ```
 *
 * @public
 */ class IRSwizzle extends IRExpression {
    /** The source expression to swizzle */ src;
    /** The swizzle pattern (e.g., 'xyz', 'rgb', 'x', 'yzw') */ hash;
    /**
   * Creates a swizzle expression
   *
   * @param src - The source vector expression
   * @param hash - The swizzle pattern using xyzw or rgba notation
   */ constructor(src, hash){
        super();
        this.src = src.addRef();
        this.hash = hash;
    }
    /**
   * Generates shader code for the swizzle operation
   *
   * @param pb - The program builder
   * @returns The swizzled shader expression
   *
   * @remarks
   * If source is a scalar number, creates a vector with that value repeated.
   * Otherwise, applies the swizzle notation to the source expression.
   */ create(pb) {
        const src = this.src.create(pb);
        // @ts-ignore
        return typeof src === 'number' ? pb[`vec${this.hash.length}`](src) : src[this.hash];
    }
}
/**
 * IR expression for type casting with padding
 *
 * @remarks
 * Converts a value to a different vector type by padding with zeros.
 * Used when a smaller vector needs to be converted to a larger one.
 *
 * @example
 * ```typescript
 * // Cast float to vec3: vec3(value, 0, 0)
 * const vec3 = new IRCast(floatExpr, 'vec3', 2);
 *
 * // Cast vec2 to vec4: vec4(value.xy, 0, 0)
 * const vec4 = new IRCast(vec2Expr, 'vec4', 2);
 * ```
 *
 * @public
 */ class IRCast extends IRExpression {
    /** The source expression to cast */ src;
    /** The values to be filled */ fill;
    /** The target type name (e.g., 'vec2', 'vec3', 'vec4') */ type;
    /** Number of zero components to append */ cast;
    /**
   * Creates a type cast expression
   *
   * @param src - The source expression
   * @param type - The target type name
   * @param cast - Number of zero components to append
   */ constructor(src, type, cast, fill){
        super();
        this.src = src.addRef();
        this.cast = cast;
        this.fill = fill?.slice() ?? null;
        this.type = type;
    }
    /**
   * Generates shader code for the type cast
   *
   * @param pb - The program builder
   * @returns The casted shader expression
   *
   * @remarks
   * Creates a vector constructor with the source value followed by zeros.
   * Example: vec4(src, 0, 0) for casting vec2 to vec4.
   */ create(pb) {
        // @ts-ignore
        return pb[this.type](this.src.create(pb), ...this.fill ?? Array.from({
            length: this.cast
        }).fill(0));
    }
}
/**
 * IR expression for texture sampling
 *
 * @remarks
 * Represents a texture lookup operation with optional normal map decoding.
 * Handles both color and normal map sampling with appropriate transformations.
 * Uses reference counting to cache the sampled result if used multiple times.
 *
 * @example
 * ```typescript
 * // Sample color texture
 * const color = new IRSampleTexture(texExpr, uvExpr, 'Color');
 *
 * // Sample normal map (automatically decodes from [0,1] to [-1,1])
 * const normal = new IRSampleTexture(texExpr, uvExpr, 'Normal');
 * ```
 *
 * @public
 */ class IRSampleTexture extends IRExpression {
    /** The texture expression */ tex;
    /** The texture coordinate expression */ coord;
    /** The sampler type determining how to interpret the sampled value */ samplerType;
    /** Cached temporary variable name if result is referenced multiple times */ tmpName;
    /**
   * Creates a texture sample expression
   *
   * @param tex - The texture to sample from
   * @param coord - The texture coordinates
   * @param samplerType - How to interpret the sampled value ('Color' or 'Normal')
   */ constructor(tex, coord, samplerType){
        super();
        this.tex = tex.addRef();
        this.coord = coord.addRef();
        this.samplerType = samplerType;
        this.tmpName = '';
    }
    /** Reset for next creation */ reset() {
        this.tmpName = '';
    }
    /**
   * Generates shader code for the texture sample operation
   *
   * @param pb - The program builder
   * @returns The sampled color value
   *
   * @remarks
   * For normal maps, applies the transformation: normal * 2 - 1 to convert
   * from [0,1] texture space to [-1,1] tangent space.
   * Caches result in a temporary variable if referenced multiple times.
   *
   * @throws Error if texture coordinate is not a valid expression or array
   */ create(pb) {
        if (this.tmpName) {
            return pb.getCurrentScope()[this.tmpName];
        }
        const tex = this.tex.create(pb);
        const coord = this.coord.create(pb);
        let coordExp;
        let texExp;
        if (tex instanceof PBShaderExp) {
            texExp = tex;
        } else {
            throw new Error('Invalid texture expression');
        }
        if (coord instanceof PBShaderExp) {
            coordExp = coord;
        } else if (Array.isArray(coord)) {
            // @ts-ignore
            coordExp = pb[`vec${coord.length}`](...coord);
        } else {
            throw new Error('Invalid texture coordinate');
        }
        let exp = pb.textureSample(texExp, coordExp);
        if (this.samplerType === 'Normal') {
            exp = pb.sub(pb.mul(exp, pb.vec4(2, 2, 2, 1)), pb.vec4(1, 1, 1, 0));
        }
        if (this._ref === 1) {
            return exp;
        } else {
            this.tmpName = this.getTmpName(pb.getCurrentScope());
            pb.getCurrentScope()[this.tmpName] = exp;
            return pb.getCurrentScope()[this.tmpName];
        }
    }
}
/**
 * IR expression for texture uniform constants
 *
 * @remarks
 * Represents a texture parameter that will be bound as a uniform.
 * Includes sampler configuration for texture filtering and addressing modes.
 *
 * @example
 * ```typescript
 * const tex = new IRConstantTexture(
 *   'u_baseColorMap',
 *   'tex2d',
 *   'repeat',
 *   'repeat',
 *   'linear',
 *   'linear',
 *   'linear'
 * );
 * ```
 *
 * @public
 */ class IRConstantTexture extends IRExpression {
    /** The uniform texture variable name */ name;
    /** The texture id */ id;
    /** The texture type (e.g., 'tex2d', 'texCube') */ type;
    /** Whether this texture should loaded in sRGB color space */ sRGB;
    /** Horizontal texture addressing mode */ addressU;
    /** Vertical texture addressing mode */ addressV;
    /** Minification filter mode */ filterMin;
    /** Magnification filter mode */ filterMag;
    /** Mipmap filter mode */ filterMip;
    /** Whether texture params is used */ useParams;
    /**
   * Creates a texture constant expression
   *
   * @param name - The uniform variable name
   * @param id - The texture id
   * @param type - The texture type
   * @param sRGB - Whether this texture should be loaded in sRGB color space
   * @param addressU - Horizontal addressing mode
   * @param addressV - Vertical addressing mode
   * @param minFilter - Minification filter
   * @param magFilter - Magnification filter
   * @param mipFilter - Mipmap filter
   */ constructor(name, id, type, sRGB, addressU, addressV, minFilter, magFilter, mipFilter){
        super();
        this.name = name;
        this.id = id;
        this.type = type;
        this.sRGB = sRGB;
        this.addressU = addressU;
        this.addressV = addressV;
        this.filterMin = minFilter;
        this.filterMag = magFilter;
        this.filterMip = mipFilter;
        this.useParams = false;
    }
    /**
   * Generates shader code for the texture uniform
   *
   * @param pb - The program builder
   * @returns The texture uniform reference
   *
   * @remarks
   * Creates the uniform declaration in the global scope if not already present.
   */ create(pb) {
        if (!pb.getGlobalScope()[this.name]) {
            // @ts-ignore
            pb.getGlobalScope()[this.name] = pb[this.type]().uniform(2);
        }
        return pb.getGlobalScope()[this.name];
    }
    /**
   * Converts to a uniform texture descriptor
   *
   * @param node - The texture node
   * @returns Uniform texture descriptor with texture and sampler references
   */ asUniformTexture() {
        return {
            name: this.name,
            texture: this.id,
            wrapS: this.addressU,
            wrapT: this.addressV,
            minFilter: this.filterMin,
            magFilter: this.filterMag,
            mipFilter: this.filterMip,
            type: this.type,
            sRGB: this.sRGB
        };
    }
}
/**
 * Material Blueprint Intermediate Representation
 *
 * @remarks
 * The IR is a compiled representation of the material node graph that can be
 * efficiently translated to shader code. It performs:
 * - Type checking and validation
 * - Expression optimization (common subexpression elimination via reference counting)
 * - Uniform extraction (constants become shader uniforms)
 * - Dependency analysis (determines required vertex attributes)
 *
 * The compilation process:
 * 1. Traverse the DAG in topological order
 * 2. Build an IR expression tree
 * 3. Track uniforms and behaviors
 * 4. Generate optimized shader code from the IR
 *
 * @public
 */ class MaterialBlueprintIR {
    /** The directed acyclic graph of material nodes */ _dag;
    /** Unique hash identifying this material configuration */ _hash;
    /** Editor state snapshot at time of compilation */ _editorState;
    /** Array of all IR expressions in the graph */ _expressions;
    /** Map from graph node to IR expression index */ _expressionMap;
    /** Array of uniform scalar/vector values to be set at runtime */ _uniformValues;
    /** Array of uniform textures to be bound at runtime */ _uniformTextures;
    /** Fallback texture uniforms generated for TextureSample nodes without texture input */ _textureSampleFallbackMap;
    /** Flags indicating which shader features are used */ _behaviors;
    /** Array of named output expressions (e.g., baseColor, normal, metallic) */ _outputs;
    /**
   * Creates and compiles a material blueprint IR
   *
   * @param dag - The material node graph DAG
   * @param hash - Unique identifier for this material
   * @param editorState - Editor state snapshot
   *
   * @remarks
   * Automatically compiles the DAG during construction.
   * Check the `ok` property to verify successful compilation.
   */ constructor(dag, hash, editorState){
        this._dag = dag ?? {
            nodeMap: {},
            roots: [],
            graph: {
                outgoing: {},
                incoming: {}
            },
            order: []
        };
        this._hash = hash;
        this._editorState = editorState;
        this.compile();
    }
    /**
   * Whether the IR compiled successfully
   *
   * @returns True if compilation produced valid outputs
   */ get ok() {
        return this._outputs ? this._outputs.length > 0 : false;
    }
    /** Gets the unique hash for this material */ get hash() {
        return this._hash;
    }
    /** Gets the editor state snapshot */ get editorState() {
        return this._editorState;
    }
    /** Gets the behavior flags indicating shader requirements */ get behaviors() {
        return this._behaviors;
    }
    /** Gets the material node DAG */ get DAG() {
        return this._dag;
    }
    set DAG(dag) {
        this._dag = dag;
    }
    /** Gets the array of uniform values to set at runtime */ get uniformValues() {
        return this._uniformValues;
    }
    /** Gets the array of uniform textures to bind at runtime */ get uniformTextures() {
        return this._uniformTextures;
    }
    /**
   * Compiles the material node graph to IR
   *
   * @returns True if compilation succeeded, false otherwise
   *
   * @remarks
   * Processes all root nodes (typically a single material output node)
   * and builds IR expressions for all connected inputs.
   * Collects all required uniforms and sets behavior flags.
   *
   * Compilation fails if any required input is missing.
   */ compile() {
        this.reset();
        this._outputs = [];
        for (const root of this._dag.roots){
            const rootNode = this._dag.nodeMap[root];
            for (const input of rootNode.inputs){
                const name = input.name;
                if (input.inputNode) {
                    this._outputs.push({
                        name,
                        expr: this.ir(input).addRef()
                    });
                } else if (Array.isArray(input.defaultValue)) {
                    if (input.defaultValue.length === 1) {
                        this._outputs.push({
                            name,
                            expr: new IRConstantf(input.defaultValue[0], '').addRef()
                        });
                    } else {
                        this._outputs.push({
                            name,
                            expr: new IRConstantfv(input.defaultValue, '', `vec${input.defaultValue.length}`).addRef()
                        });
                    }
                } else if (input.required) {
                    this._outputs = null;
                    return false;
                }
                if (!input.inputNode && rootNode instanceof VertexBlockNode) {
                    if (input.name === 'Color') {
                        this._behaviors.useVertexColor = true;
                    } else if (input.name === 'UV') {
                        this._behaviors.useVertexUV = true;
                    }
                }
            }
        }
        return true;
    }
    /**
   * Generates shader code from the IR
   *
   * @param pb - The program builder to generate code with
   * @returns Array of named shader expressions, or null if IR is invalid
   *
   * @remarks
   * Translates all IR expressions to actual shader code.
   * Should be called within a shader function scope.
   *
   * @example
   * ```typescript
   * pb.fragmentShader(function() {
   *   const outputs = ir.create(this);
   *   this.baseColor = outputs.find(o => o.name === 'baseColor').exp;
   *   this.normal = outputs.find(o => o.name === 'normal').exp;
   * });
   * ```
   */ create(pb) {
        if (!this._outputs) {
            return null;
        }
        for (const expr of this._expressions){
            expr.reset();
        }
        const outputs = [];
        for (const output of this._outputs){
            outputs.push({
                name: output.name,
                exp: output.expr.create(pb)
            });
        }
        return outputs;
    }
    /**
   * Resets the IR state for recompilation
   *
   * @remarks
   * Clears all compiled data including expressions, uniforms, and behaviors.
   * Called at the start of compilation.
   */ reset() {
        this._expressions = [];
        this._expressionMap = new Map();
        this._uniformTextures = [];
        this._uniformValues = [];
        this._textureSampleFallbackMap = new Map();
        this._outputs = null;
        this._behaviors = {
            useVertexColor: false,
            useVertexUV: false
        };
    }
    /**
   * Converts a graph node to an IR expression
   *
   * @param node - The graph node to convert
   * @param output - The output slot ID
   * @param originType - Optional type override for type casting
   * @returns The corresponding IR expression
   *
   * @remarks
   * Main dispatch method that routes to specific converters based on node type.
   * Handles automatic type casting when originType differs from node output type.
   *
   * @throws Error if type casting is invalid
   */ ir(inputNode) {
        const node = inputNode.inputNode;
        const output = inputNode.inputId;
        const originType = inputNode.originType;
        const defaultValue = inputNode.defaultValue;
        let expr = null;
        if (node instanceof ConstantScalarNode) {
            expr = this.constantf(node, output);
        } else if (node instanceof ConstantVec2Node || node instanceof ConstantVec3Node || node instanceof ConstantVec4Node) {
            expr = this.constantfv(node, output);
        } else if (node instanceof ConstantBooleanNode) {
            expr = this.constantb(node, output);
        } else if (node instanceof ConstantBVec2Node || node instanceof ConstantBVec3Node || node instanceof ConstantBVec4Node) {
            expr = this.constantbv(node, output);
        } else if (node instanceof BaseTextureNode) {
            expr = this.constantTexture(node, output);
        } else if (node instanceof TextureSampleNode) {
            expr = this.textureSample(node, output);
        } else if (node instanceof PannerNode) {
            expr = this.panner(node, output);
        } else if (node instanceof MakeVectorNode) {
            expr = this.makeVector(node, output);
        } else if (node instanceof SwizzleNode) {
            expr = this.swizzle(node, output);
        } else if (node instanceof CompComparisonNode) {
            expr = this.comparison(node, output);
        } else if (node instanceof AnyConditionNode) {
            expr = this.any(node, output);
        } else if (node instanceof AllConditionNode) {
            expr = this.all(node, output);
        } else if (node instanceof SelectionNode) {
            expr = this.selection(node, output);
        } else if (node instanceof ResolveVertexPositionNode) {
            expr = this.resolveVertexPosition(node, output);
        } else if (node instanceof ResolveVertexNormalNode) {
            expr = this.resolveVertexNormal(node, output);
        } else if (node instanceof ResolveVertexTangentNode) {
            expr = this.resolveVertexTangent(node, output);
        } else if (node instanceof Hash1Node) {
            expr = this.hash1(node, output);
        } else if (node instanceof Hash2Node) {
            expr = this.hash2(node, output);
        } else if (node instanceof Hash3Node) {
            expr = this.hash3(node, output);
        } else if (node instanceof SimplexNoise2DNode) {
            expr = this.simplexNoise2d(node, output);
        } else if (node instanceof PerlinNoise2DNode) {
            expr = this.perlinNoise2d(node, output);
        } else if (node instanceof GenericMathNode) {
            expr = this.func(node, output);
        } else if (node instanceof TransformNode) {
            expr = this.transform(node, output);
        } else if (node instanceof RotateAboutAxisNode) {
            expr = this.rotateAboutAxis(node, output);
        } else if (node instanceof VertexColorNode) {
            expr = this.vertexColor(node, output);
        } else if (node instanceof VertexUVNode) {
            expr = this.vertexUV(node, output);
        } else if (node instanceof VertexPositionNode) {
            expr = this.vertexPosition(node, output);
        } else if (node instanceof PixelWorldPositionNode) {
            expr = this.pixelWorldPosition(node, output);
        } else if (node instanceof VertexNormalNode) {
            expr = this.vertexNormal(node, output);
        } else if (node instanceof PixelNormalNode) {
            expr = this.pixelNormal(node, output);
        } else if (node instanceof VertexTangentNode) {
            expr = this.vertexTangent(node, output);
        } else if (node instanceof VertexBinormalNode) {
            expr = this.vertexBinormal(node, output);
        } else if (node instanceof ViewMatrixNode) {
            expr = this.viewMatrix(node, output);
        } else if (node instanceof ProjectionMatrixNode) {
            expr = this.projectionMatrix(node, output);
        } else if (node instanceof ViewProjMatrixNode) {
            expr = this.viewProjectionMatrix(node, output);
        } else if (node instanceof InvProjMatrixNode) {
            expr = this.invProjectionMatrix(node, output);
        } else if (node instanceof InvViewProjMatrixNode) {
            expr = this.invViewProjectionMatrix(node, output);
        } else if (node instanceof BillboardMatrixNode) {
            expr = this.billboardMatrix(node, output);
        } else if (node instanceof CameraPositionNode) {
            expr = this.cameraPosition(node, output);
        } else if (node instanceof CameraVectorNode) {
            expr = this.cameraVector(node, output);
        } else if (node instanceof CameraNearFarNode) {
            expr = this.cameraNearFar(node, output);
        } else if (node instanceof SkyEnvTextureNode) {
            expr = this.skyEnvTexture(node, output);
        } else if (node instanceof ElapsedTimeNode) {
            expr = this.elapsedTime(node, output);
        } else if (node instanceof FunctionCallNode) {
            expr = this.functionCall(node, output);
        } else if (node instanceof FunctionInputNode) {
            expr = this.functionInput(node, output);
        } else if (node instanceof FunctionOutputNode) {
            expr = this.functionOutput(node, output);
        }
        if (expr && originType) {
            const outputType = node.getOutputType(output);
            if (originType !== outputType) {
                if (originType === 'float') {
                    if (outputType !== 'vec2' && outputType !== 'vec3' && outputType !== 'vec4') {
                        throw new Error(`Cannot cast type \`${outputType}\` to \`${originType}`);
                    }
                    return new IRSwizzle(expr, 'x');
                } else if (outputType === 'float') {
                    if (originType !== 'vec2' && originType !== 'vec3' && originType !== 'vec4') {
                        throw new Error(`Cannot cast type \`${outputType}\` to \`${originType}`);
                    }
                    return new IRCast(expr, originType, 0);
                } else if (outputType === 'vec2' || outputType === 'vec3' || outputType === 'vec4') {
                    const nOut = Number(outputType[outputType.length - 1]);
                    const nOrg = Number(originType[originType.length - 1]);
                    if (nOut > nOrg) {
                        return new IRSwizzle(expr, 'xyzw'.slice(0, nOrg));
                    } else {
                        return new IRCast(expr, originType, nOrg - nOut, defaultValue?.slice(nOut - nOrg));
                    }
                } else {
                    throw new Error(`Cannot cast type \`${outputType}\` to \`${originType}`);
                }
            }
        }
        return expr;
    }
    /**
   * Gets or creates an IR expression for a graph node
   *
   * @param node - The graph node
   * @param outputId - The output slot ID
   * @param ctor - The IR expression constructor
   * @param args - Constructor arguments
   * @returns The cached or newly created IR expression
   *
   * @remarks
   * Implements common subexpression elimination by caching expressions per node.
   * Handles output slot swizzling and type casting.
   * Collects uniform values and textures.
   */ getOrCreateIRExpression(node, outputId, ctor, ...args) {
        let ir;
        if (!this._expressionMap.has(node)) {
            ir = new ctor(...args);
            this._expressions.push(ir);
            this._expressionMap.set(node, this._expressions.length - 1);
            const uniformValue = ir.asUniformValue(node);
            if (uniformValue) {
                this._uniformValues.push(uniformValue);
            }
            const uniformTexture = ir.asUniformTexture(node);
            if (uniformTexture) {
                this._uniformTextures.push(uniformTexture);
            }
        } else {
            ir = this._expressions[this._expressionMap.get(node)];
        }
        if (!ir.outputs[outputId]) {
            const output = node.outputs.find((v)=>v.id === outputId);
            ir.outputs[outputId] = ir;
            if (output.swizzle) {
                ir.outputs[outputId] = new IRSwizzle(ir.outputs[outputId], output.swizzle);
            }
        }
        return ir.outputs[outputId];
    }
    /** Converts a MakeVector node to IR */ makeVector(node, output) {
        const params = [];
        for (const input of node.inputs){
            if (input.inputNode) {
                params.push(this.ir(input));
            }
        }
        const funcName = node.getOutputType(output);
        return this.getOrCreateIRExpression(node, output, IRFunc, params, funcName);
    }
    /** Converts a swizzle node to IR */ swizzle(node, output) {
        return this.getOrCreateIRExpression(node, output, IRSwizzle, this.ir(node.inputs[0]), node.swizzle);
    }
    /** Converts a comparison node to IR */ comparison(node, output) {
        return this.getOrCreateIRExpression(node, output, IRComparison, this.ir(node.inputs[0]), this.ir(node.inputs[1]), node.mode);
    }
    /** Converts an any-condition node to IR */ any(node, output) {
        return this.getOrCreateIRExpression(node, output, IRAny, this.ir(node.inputs[0]));
    }
    /** Converts an all-condition node to IR */ all(node, output) {
        return this.getOrCreateIRExpression(node, output, IRAll, this.ir(node.inputs[0]));
    }
    /** Converts a selection node to IR */ selection(node, output) {
        return this.getOrCreateIRExpression(node, output, IRSelection, this.ir(node.inputs[0]), this.ir(node.inputs[1]), this.ir(node.inputs[2]));
    }
    /** Converts a Transform node to IR */ transform(node, output) {
        const params = [];
        for (const input of node.inputs){
            if (input.inputNode) {
                params.push(this.ir(input));
            }
        }
        return this.getOrCreateIRExpression(node, output, IRFunc, params, 'mul');
    }
    /** Converts a generic math function node to IR */ func(node, output) {
        const params = [];
        for (const input of node.inputs){
            if (input.inputNode) {
                params.push(this.ir(input));
            }
        }
        const funcName = node.func;
        return this.getOrCreateIRExpression(node, output, IRFunc, params, funcName);
    }
    /** Converts a function output node to IR */ functionOutput(node, output) {
        const input = this.ir(node.inputs[0]);
        return this.getOrCreateIRExpression(node, output, IRFunctionOutput, input);
    }
    /** Converts a rotate-about-axis node to IR */ rotateAboutAxis(node, output) {
        const axis = this.ir(node.inputs[0]);
        const angleTurns = node.inputs[1].inputNode ? this.ir(node.inputs[1]) : new IRConstantf(node.inputs[1].defaultValue?.[0] ?? 0, '');
        const pivot = node.inputs[2].inputNode ? this.ir(node.inputs[2]) : new IRConstantfv(node.inputs[2].defaultValue ?? [
            0,
            0,
            0
        ], '', 'vec3');
        const position = this.ir(node.inputs[3]);
        const angleRad = new IRFunc([
            angleTurns,
            6.283185307179586
        ], 'mul'); // turns -> radians
        const cosA = new IRFunc([
            angleRad
        ], 'cos');
        const sinA = new IRFunc([
            angleRad
        ], 'sin');
        const p = new IRFunc([
            position,
            pivot
        ], 'sub');
        const term1 = new IRFunc([
            p,
            cosA
        ], 'mul');
        const term2 = new IRFunc([
            new IRFunc([
                axis,
                p
            ], 'cross'),
            sinA
        ], 'mul');
        const term3 = new IRFunc([
            axis,
            new IRFunc([
                new IRFunc([
                    axis,
                    p
                ], 'dot'),
                new IRFunc([
                    1,
                    cosA
                ], 'sub')
            ], 'mul')
        ], 'mul');
        const rotated = new IRFunc([
            new IRFunc([
                term1,
                term2
            ], 'add'),
            term3
        ], 'add');
        return this.getOrCreateIRExpression(node, output, IRFunc, [
            rotated,
            pivot
        ], 'add');
    }
    /** Converts a panner node to IR */ panner(node, output) {
        const coordInput = node.inputs[0];
        const timeInput = node.inputs[1];
        const speedInput = node.inputs[2];
        const coord = coordInput.inputNode ? this.ir(coordInput) : (()=>{
            this._behaviors.useVertexUV = true;
            return new IRInput((scope)=>scope.zVertexUV);
        })();
        const time = timeInput.inputNode ? this.ir(timeInput) : new IRInput((scope)=>ShaderHelper.getElapsedTime(scope));
        const speed = speedInput.inputNode ? this.ir(speedInput) : new IRConstantfv(speedInput.defaultValue ?? [
            0,
            0
        ], '', 'vec2');
        return this.getOrCreateIRExpression(node, output, IRFunc, [
            coord,
            new IRFunc([
                time,
                speed
            ], 'mul')
        ], 'add');
    }
    /** Converts a vertex color input node to IR */ vertexColor(node, output) {
        this._behaviors.useVertexColor = true;
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>scope.zVertexColor);
    }
    /** Converts a vertex UV input node to IR */ vertexUV(node, output) {
        this._behaviors.useVertexUV = true;
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>{
            return scope.zVertexUV;
        });
    }
    /** Converts a vertex normal input node to IR */ vertexNormal(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>scope.$builder.getDevice().type === 'vertex' ? scope.$getVertexAttrib('normal') ?? ShaderHelper.resolveVertexNormal(scope) : scope.$builder.normalize(scope.zVertexNormal));
    }
    /** Converts a pixel normal input node to IR */ pixelNormal(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>scope.$builder.getDevice().type === 'vertex' ? scope.$getVertexAttrib('normal') ?? ShaderHelper.resolveVertexNormal(scope) : scope.$builder.normalize(scope.zVertexNormal));
    }
    /** Converts a vertex tangent input node to IR */ vertexTangent(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>scope.$builder.getDevice().type === 'vertex' ? scope.$getVertexAttrib('tangent') ?? ShaderHelper.resolveVertexTangent(scope) : scope.$builder.normalize(scope.zVertexTangent));
    }
    /** Converts a vertex binormal input node to IR */ vertexBinormal(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>scope.$builder.normalize(scope.zVertexBinormal));
    }
    /** Converts a vertex position input node to IR */ vertexPosition(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>{
            if (scope.$builder.getDevice().type === 'vertex') {
                const worldMatrix = ShaderHelper.getWorldMatrix(scope);
                const basePos = ShaderHelper.resolveVertexPosition(scope);
                return scope.$builder.mul(worldMatrix, scope.$builder.vec4(basePos, 1)).xyz;
            }
            return scope.zWorldPos;
        });
    }
    /** Converts a pixel world position input node to IR */ pixelWorldPosition(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>{
            if (scope.$builder.getDevice().type === 'vertex') {
                const worldMatrix = ShaderHelper.getWorldMatrix(scope);
                const basePos = ShaderHelper.resolveVertexPosition(scope);
                return scope.$builder.mul(worldMatrix, scope.$builder.vec4(basePos, 1)).xyz;
            }
            return scope.zWorldPos;
        });
    }
    /** Converts a view matrix input node to IR */ viewMatrix(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getViewMatrix(scope));
    }
    /** Converts a projection matrix input node to IR */ projectionMatrix(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getProjectionMatrix(scope));
    }
    /** Converts a view-projection matrix input node to IR */ viewProjectionMatrix(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getViewProjectionMatrix(scope));
    }
    /** Converts an inverse projection matrix input node to IR */ invProjectionMatrix(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getInvProjectionMatrix(scope));
    }
    /** Converts an inverse view-projection matrix input node to IR */ invViewProjectionMatrix(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getInvViewProjectionMatrix(scope));
    }
    /** Converts a camera position input node to IR */ cameraPosition(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getCameraPosition(scope));
    }
    /** Converts a camera vector input node to IR */ cameraVector(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>{
            const pb = scope.$builder;
            const worldPos = pb.getDevice().type === 'vertex' ? pb.mul(ShaderHelper.getWorldMatrix(scope), pb.vec4(ShaderHelper.resolveVertexPosition(scope), 1)).xyz : scope.zWorldPos;
            return pb.normalize(pb.sub(ShaderHelper.getCameraPosition(scope), worldPos));
        });
    }
    /** Converts a camera near/far input node to IR */ cameraNearFar(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getCameraParams(scope).xy);
    }
    /** Converts a sky environment texture input node to IR */ skyEnvTexture(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getBakedSkyTexture(scope));
    }
    /** Converts an elapsed time input node to IR */ elapsedTime(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>ShaderHelper.getElapsedTime(scope));
    }
    /** Converts a function input node to IR */ functionInput(node, output) {
        return this.getOrCreateIRExpression(node, output, IRInput, (scope)=>scope[node.name]);
    }
    /** Converts an vertex position resolver node to IR */ resolveVertexPosition(node, output) {
        return this.getOrCreateIRExpression(node, output, IRFunc, [], (pb)=>ShaderHelper.resolveVertexPosition(pb.getCurrentScope()));
    }
    /** Converts an vertex normal resolver node to IR */ resolveVertexNormal(node, output) {
        return this.getOrCreateIRExpression(node, output, IRFunc, [], (pb)=>ShaderHelper.resolveVertexNormal(pb.getCurrentScope()));
    }
    /** Converts an vertex tangent resolver node to IR */ resolveVertexTangent(node, output) {
        return this.getOrCreateIRExpression(node, output, IRFunc, [], (pb)=>ShaderHelper.resolveVertexTangent(pb.getCurrentScope()));
    }
    /** Converts an billboard matrix input node to IR */ billboardMatrix(node, output) {
        return this.getOrCreateIRExpression(node, output, IRFunc, [], (pb)=>{
            pb.func('Z_calcBillboardMatrix', [], function() {
                this.$l.objScale = pb.vec3(pb.length(ShaderHelper.getWorldMatrix(this)[0].xyz), pb.length(ShaderHelper.getWorldMatrix(this)[1].xyz), pb.length(ShaderHelper.getWorldMatrix(this)[2].xyz));
                this.$l.rotation = pb.mat3(pb.div(ShaderHelper.getWorldMatrix(this)[0].xyz, this.objScale.x), pb.div(ShaderHelper.getWorldMatrix(this)[1].xyz, this.objScale.y), pb.div(ShaderHelper.getWorldMatrix(this)[2].xyz, this.objScale.z));
                this.$l.cameraForward = pb.normalize(ShaderHelper.getInvViewMatrix(this)[2].xyz);
                this.$l.cameraUp = pb.normalize(ShaderHelper.getInvViewMatrix(this)[1].xyz);
                this.$l.cameraRight = pb.normalize(ShaderHelper.getInvViewMatrix(this)[0].xyz);
                this.$l.objForward = pb.normalize(pb.div(pb.mul(this.cameraForward, this.rotation), this.objScale));
                this.$l.objUp = pb.normalize(pb.div(pb.mul(this.cameraUp, this.rotation), this.objScale));
                this.$l.objRight = pb.normalize(pb.div(pb.mul(this.cameraRight, this.rotation), this.objScale));
                // make ortho
                /*
        this.objRight = pb.normalize(
          pb.sub(this.objRight, pb.mul(this.objUp, pb.dot(this.objRight, this.objUp)))
        );
        this.objForward = pb.cross(this.objUp, this.objRight);
        */ // make rotation matrix
                this.$l.billboardMatrix = pb.mat3(this.objRight.x, this.objUp.x, this.objForward.x, this.objRight.y, this.objUp.y, this.objForward.y, this.objRight.z, this.objUp.z, this.objForward.z);
                this.$return(pb.transpose(this.billboardMatrix));
            });
            return pb.getGlobalScope().Z_calcBillboardMatrix();
        });
    }
    /** Converts a hash1 node to IR */ hash1(node, output) {
        const params = [];
        const input = node.inputs[0];
        ASSERT(!!input.inputNode);
        const type = input.inputNode.getOutputType(input.inputId);
        ASSERT(type === 'float' || type === 'vec2' || type === 'vec3');
        params.push(this.ir(input));
        return this.getOrCreateIRExpression(node, output, IRFunc, params, (pb, ...params)=>{
            const scope = pb.getCurrentScope();
            const seed = params[0];
            return type === 'float' ? hash11(scope, seed) : type === 'vec2' ? hash21(scope, seed) : hash31(scope, seed);
        });
    }
    /** Converts a hash2 node to IR */ hash2(node, output) {
        const params = [];
        const input = node.inputs[0];
        ASSERT(!!input.inputNode);
        const type = input.inputNode.getOutputType(input.inputId);
        ASSERT(type === 'float' || type === 'vec2' || type === 'vec3');
        params.push(this.ir(input));
        return this.getOrCreateIRExpression(node, output, IRFunc, params, (pb, ...params)=>{
            const scope = pb.getCurrentScope();
            const seed = params[0];
            return type === 'float' ? hash12(scope, seed) : type === 'vec2' ? hash22(scope, seed) : hash32(scope, seed);
        });
    }
    /** Converts a hash3 node to IR */ hash3(node, output) {
        const params = [];
        const input = node.inputs[0];
        ASSERT(!!input.inputNode);
        const type = input.inputNode.getOutputType(input.inputId);
        ASSERT(type === 'float' || type === 'vec2' || type === 'vec3');
        params.push(this.ir(input));
        return this.getOrCreateIRExpression(node, output, IRFunc, params, (pb, ...params)=>{
            const scope = pb.getCurrentScope();
            const seed = params[0];
            return type === 'float' ? hash13(scope, seed) : type === 'vec2' ? hash23(scope, seed) : hash33(scope, seed);
        });
    }
    /** Converts a simple noise node to IR */ simplexNoise2d(node, output) {
        const params = [];
        for (const input of node.inputs){
            if (input.inputNode) {
                params.push(this.ir(input));
            }
        }
        return this.getOrCreateIRExpression(node, output, IRFunc, params, (pb, ...params)=>{
            pb.func(node.toString(), [
                pb.vec2('uv'),
                pb.float('scale')
            ], function() {
                this.$l.t = pb.float(0);
                let freq = 1;
                let amp = 0.5 * 0.5 * 0.5;
                this.t = pb.add(this.t, pb.mul(valueNoise(this, pb.mul(this.uv, this.scale, 1 / freq)), amp));
                freq *= 2;
                amp *= 2;
                this.t = pb.add(this.t, pb.mul(valueNoise(this, pb.mul(this.uv, this.scale, 1 / freq)), amp));
                freq *= 2;
                amp *= 2;
                this.t = pb.add(this.t, pb.mul(valueNoise(this, pb.mul(this.uv, this.scale, 1 / freq)), amp));
                this.$return(this.t);
            });
            return pb.getGlobalScope()[node.toString()](params[0], params[1]);
        });
    }
    /** Converts a perlin noise node to IR */ perlinNoise2d(node, output) {
        const params = [];
        for (const input of node.inputs){
            if (input.inputNode) {
                params.push(this.ir(input));
            }
        }
        return this.getOrCreateIRExpression(node, output, IRFunc, params, (pb, ...params)=>{
            pb.func('Z_perlinNoise2dDir', [
                pb.vec2('uv')
            ], function() {
                this.$l.p = pb.mod(this.uv, pb.vec2(289));
                this.$l.x = pb.add(pb.mod(pb.mul(pb.add(pb.mul(this.p.x, 34), 1), this.p.x), 289), this.p.y);
                this.x = pb.mod(pb.mul(pb.add(pb.mul(this.x, 34), 1), this.x), 289);
                this.x = pb.sub(pb.mul(pb.fract(pb.div(this.x, 41)), 2), 1);
                this.$return(pb.normalize(pb.vec2(pb.sub(this.x, pb.floor(pb.add(this.x, 0.5))), pb.sub(pb.abs(this.x), 0.5))));
            });
            pb.func('Z_perlinNoise2dImpl', [
                pb.vec2('uv')
            ], function() {
                this.$l.i = pb.floor(this.uv);
                this.$l.f = pb.fract(this.uv);
                this.$l.r00 = pb.dot(this.Z_perlinNoise2dDir(this.i), this.f);
                this.$l.r01 = pb.dot(this.Z_perlinNoise2dDir(pb.add(this.i, pb.vec2(0, 1))), pb.sub(this.f, pb.vec2(0, 1)));
                this.$l.r10 = pb.dot(this.Z_perlinNoise2dDir(pb.add(this.i, pb.vec2(1, 0))), pb.sub(this.f, pb.vec2(1, 0)));
                this.$l.r11 = pb.dot(this.Z_perlinNoise2dDir(pb.add(this.i, pb.vec2(1, 1))), pb.sub(this.f, pb.vec2(1, 1)));
                this.f = pb.mul(this.f, this.f, this.f, pb.add(pb.mul(this.f, pb.sub(pb.mul(this.f, 6), pb.vec2(15))), 10));
                this.$return(pb.mix(pb.mix(this.r00, this.r01, this.f.y), pb.mix(this.r10, this.r11, this.f.y), this.f.x));
            });
            pb.func(node.toString(), [
                pb.vec2('uv'),
                pb.float('scale')
            ], function() {
                this.$return(pb.add(this.Z_perlinNoise2dImpl(pb.mul(this.uv, this.scale)), 0.5));
            });
            return pb.getGlobalScope()[node.toString()](params[0], params[1]);
        });
    }
    /** Converts a function call node to IR */ functionCall(node, output) {
        const args = node.inputs.map((input)=>{
            return this.ir(input);
        });
        return this.getOrCreateIRExpression(node, output, IRCallFunc, node, args);
    }
    /** Converts a scalar constant node to IR */ constantf(node, output) {
        return this.getOrCreateIRExpression(node, output, IRConstantf, node.x, node.paramName);
    }
    /** Converts a vector constant node to IR */ constantfv(node, output) {
        const value = node instanceof ConstantVec2Node ? [
            node.x,
            node.y
        ] : node instanceof ConstantVec3Node ? [
            node.x,
            node.y,
            node.z
        ] : [
            node.x,
            node.y,
            node.z,
            node.w
        ];
        return this.getOrCreateIRExpression(node, output, IRConstantfv, value, node.paramName, `vec${value.length}`);
    }
    /** Converts a scalar boolean constant node to IR */ constantb(node, output) {
        return this.getOrCreateIRExpression(node, output, IRConstantb, node.x);
    }
    /** Converts a boolean vector constant node to IR */ constantbv(node, output) {
        const value = node instanceof ConstantBVec2Node ? [
            node.x,
            node.y
        ] : node instanceof ConstantBVec3Node ? [
            node.x,
            node.y,
            node.z
        ] : [
            node.x,
            node.y,
            node.z,
            node.w
        ];
        return this.getOrCreateIRExpression(node, output, IRConstantbv, value);
    }
    /** Converts a texture constant node to IR */ constantTexture(node, output) {
        return this.getOrCreateIRExpression(node, output, IRConstantTexture, node.paramName, node.textureId, node.getOutputType(1), node.sRGB, node.addressU, node.addressV, node.filterMin, node.filterMag, node.filterMip);
    }
    /** Converts a texture sample node to IR */ textureSample(node, output) {
        const tex = node.inputs[0].inputNode ? this.ir(node.inputs[0]) : this.textureSampleFallbackTexture(node);
        const coord = node.inputs[1].inputNode ? this.ir(node.inputs[1]) : (()=>{
            this._behaviors.useVertexUV = true;
            return new IRInput((scope)=>scope.zVertexUV);
        })();
        return this.getOrCreateIRExpression(node, output, IRSampleTexture, tex, coord, node.samplerType);
    }
    /** Gets or creates fallback texture uniform expression for texture sample nodes */ textureSampleFallbackTexture(node) {
        let ir = this._textureSampleFallbackMap.get(node);
        if (!ir) {
            ir = new IRConstantTexture(node.paramName, node.textureId, 'tex2D', node.sRGB, node.addressU, node.addressV, node.filterMin, node.filterMag, node.filterMip);
            this._expressions.push(ir);
            this._textureSampleFallbackMap.set(node, ir);
            const uniformTexture = ir.asUniformTexture();
            if (uniformTexture) {
                this._uniformTextures.push(uniformTexture);
            }
        }
        return ir;
    }
}

export { MaterialBlueprintIR };
//# sourceMappingURL=ir.js.map
