import '../../../assert/js/assert.js';
import '../../../globals.js';
// Copyright 2023-2024, University of Colorado Boulder
/**
 * RenderProgram for a radial blend (essentially a chunk of a radial gradient with only a linear transition between
 * two things.
 *
 * RenderRadialBlend will interpolate between two different RenderPrograms based on the location. It will evaluate
 * `clamp( ( averageFragmentRadius - radius0 ) / ( radius1 - radius0 ), 0, 1 )`, and will linearly blend
 * between the "zero" program (when the value is 0) and the "one" program (when the value is 1).
 *
 * It can be used in a standalone way, however it is primarily meant to be used when a `RenderRadialGradient`
 * is circular, and is split into each radial-linear partition.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
import { alpenglow, LinearEdge, RenderColor, RenderInstructionComputeBlendRatio, RenderInstructionLinearBlend, RenderInstructionLocation, RenderInstructionReturn, RenderProgram } from '../imports.js';
import Vector2 from '../../../dot/js/Vector2.js';
import Matrix3 from '../../../dot/js/Matrix3.js';
const scratchRadialBlendVector = new Vector2(0, 0);
const scratchVectorA = new Vector2(0, 0);
const scratchVectorB = new Vector2(0, 0);
const scratchVectorC = new Vector2(0, 0);
const scratchVectorD = new Vector2(0, 0);
export var RenderRadialBlendAccuracy;
(function (RenderRadialBlendAccuracy) {
    RenderRadialBlendAccuracy[RenderRadialBlendAccuracy["Accurate"] = 0] = "Accurate";
    RenderRadialBlendAccuracy[RenderRadialBlendAccuracy["Centroid"] = 1] = "Centroid";
    RenderRadialBlendAccuracy[RenderRadialBlendAccuracy["PixelCenter"] = 2] = "PixelCenter";
})(RenderRadialBlendAccuracy || (RenderRadialBlendAccuracy = {}));
alpenglow.register('RenderRadialBlendAccuracy', RenderRadialBlendAccuracy);
export default class RenderRadialBlend extends RenderProgram {
    constructor(transform, radius0, radius1, accuracy, zero, one, logic) {
        assert && assert(transform.isFinite());
        assert && assert(isFinite(radius0) && radius0 >= 0);
        assert && assert(isFinite(radius1) && radius1 >= 0);
        assert && assert(radius0 !== radius1);
        super([zero, one], zero.isFullyTransparent && one.isFullyTransparent, zero.isFullyOpaque && one.isFullyOpaque, accuracy === RenderRadialBlendAccuracy.Accurate, accuracy === RenderRadialBlendAccuracy.Accurate, 
        // TODO: Revert this to just Centroid once the we get the WGSL version with integration working!
        accuracy === RenderRadialBlendAccuracy.Centroid || accuracy === RenderRadialBlendAccuracy.Accurate);
        this.transform = transform;
        this.radius0 = radius0;
        this.radius1 = radius1;
        this.accuracy = accuracy;
        this.zero = zero;
        this.one = one;
        this.logic = logic || new RenderRadialBlendLogic(this.transform, this.radius0, this.radius1, this.accuracy);
    }
    getName() {
        return 'RenderRadialBlend';
    }
    withChildren(children) {
        assert && assert(children.length === 2);
        return new RenderRadialBlend(this.transform, this.radius0, this.radius1, this.accuracy, children[0], children[1], this.logic);
    }
    transformed(transform) {
        return new RenderRadialBlend(transform.timesMatrix(this.transform), this.radius0, this.radius1, this.accuracy, this.zero.transformed(transform), this.one.transformed(transform));
    }
    equalsTyped(other) {
        return this.transform.equals(other.transform) &&
            this.radius0 === other.radius0 &&
            this.radius1 === other.radius1 &&
            this.accuracy === other.accuracy;
    }
    getSimplified(children) {
        const zero = children[0];
        const one = children[1];
        if (zero.isFullyTransparent && one.isFullyTransparent) {
            return RenderColor.TRANSPARENT;
        }
        else if (zero.equals(one)) {
            return zero;
        }
        else {
            return null;
        }
    }
    evaluate(context) {
        const t = this.logic.computeLinearValue(context);
        if (t <= 0) {
            return this.zero.evaluate(context);
        }
        else if (t >= 1) {
            return this.one.evaluate(context);
        }
        else {
            return RenderColor.ratioBlend(this.zero.evaluate(context), this.one.evaluate(context), t);
        }
    }
    writeInstructions(instructions) {
        const zeroLocation = new RenderInstructionLocation();
        const oneLocation = new RenderInstructionLocation();
        const blendLocation = new RenderInstructionLocation();
        instructions.push(new RenderInstructionComputeBlendRatio(this.logic, zeroLocation, oneLocation, blendLocation));
        instructions.push(zeroLocation);
        this.zero.writeInstructions(instructions);
        instructions.push(RenderInstructionReturn.INSTANCE);
        instructions.push(oneLocation);
        this.one.writeInstructions(instructions);
        instructions.push(RenderInstructionReturn.INSTANCE);
        instructions.push(blendLocation);
        instructions.push(RenderInstructionLinearBlend.INSTANCE);
    }
    serialize() {
        return {
            type: 'RenderRadialBlend',
            transform: [
                this.transform.m00(), this.transform.m01(), this.transform.m02(),
                this.transform.m10(), this.transform.m11(), this.transform.m12(),
                this.transform.m20(), this.transform.m21(), this.transform.m22()
            ],
            radius0: this.radius0,
            radius1: this.radius1,
            accuracy: this.accuracy,
            zero: this.zero.serialize(),
            one: this.one.serialize()
        };
    }
    static deserialize(obj) {
        return new RenderRadialBlend(Matrix3.rowMajor(obj.transform[0], obj.transform[1], obj.transform[2], obj.transform[3], obj.transform[4], obj.transform[5], obj.transform[6], obj.transform[7], obj.transform[8]), obj.radius0, obj.radius1, obj.accuracy, RenderProgram.deserialize(obj.zero), RenderProgram.deserialize(obj.one));
    }
}
alpenglow.register('RenderRadialBlend', RenderRadialBlend);
export class RenderRadialBlendLogic {
    constructor(transform, radius0, radius1, accuracy) {
        this.transform = transform;
        this.radius0 = radius0;
        this.radius1 = radius1;
        this.accuracy = accuracy;
        this.inverseTransform = transform.inverted();
    }
    equals(other) {
        return this.transform.equalsEpsilon(other.transform, 1e-6) &&
            Math.abs(this.radius0 - other.radius0) < 1e-6 &&
            Math.abs(this.radius1 - other.radius1) < 1e-6 &&
            this.accuracy === other.accuracy;
    }
    computeLinearValue(context) {
        // TODO: flag to control whether this gets set? TODO: Flag to just use centroid
        let averageDistance;
        if (this.accuracy === RenderRadialBlendAccuracy.Accurate) {
            assert && assert(context.hasArea());
            if (context.face) {
                averageDistance = context.face.getAverageDistanceTransformedToOrigin(this.inverseTransform, context.area);
            }
            else {
                // NOTE: Do the equivalent of the above, but without creating a face and a ton of garbage
                const p0 = this.inverseTransform.multiplyVector2(scratchVectorA.setXY(context.minX, context.minY));
                const p1 = this.inverseTransform.multiplyVector2(scratchVectorB.setXY(context.maxX, context.minY));
                const p2 = this.inverseTransform.multiplyVector2(scratchVectorC.setXY(context.maxX, context.maxY));
                const p3 = this.inverseTransform.multiplyVector2(scratchVectorD.setXY(context.minX, context.maxY));
                // Needs CCW orientation
                averageDistance = (LinearEdge.evaluateLineIntegralDistance(p0.x, p0.y, p1.x, p1.y) +
                    LinearEdge.evaluateLineIntegralDistance(p1.x, p1.y, p2.x, p2.y) +
                    LinearEdge.evaluateLineIntegralDistance(p2.x, p2.y, p3.x, p3.y) +
                    LinearEdge.evaluateLineIntegralDistance(p3.x, p3.y, p0.x, p0.y)) / (context.area * this.inverseTransform.getSignedScale());
                assert && assert(averageDistance === context.getFace().getAverageDistanceTransformedToOrigin(this.inverseTransform, context.area));
            }
        }
        else if (this.accuracy === RenderRadialBlendAccuracy.Centroid) {
            assert && assert(context.hasCentroid());
            const localPoint = scratchRadialBlendVector.set(context.centroid);
            this.inverseTransform.multiplyVector2(localPoint);
            averageDistance = localPoint.magnitude;
        }
        else if (this.accuracy === RenderRadialBlendAccuracy.PixelCenter) {
            const localPoint = context.writeBoundsCentroid(scratchRadialBlendVector);
            this.inverseTransform.multiplyVector2(localPoint);
            averageDistance = localPoint.magnitude;
        }
        else {
            throw new Error('unreachable');
        }
        assert && assert(isFinite(averageDistance));
        // if ( assert ) {
        //
        //   const maxDistance = Math.sqrt( ( maxX - minX ) ** 2 + ( maxY - minY ) ** 2 );
        //   assert( Math.abs( averageDistance - localPoint.magnitude ) < maxDistance * 5 );
        // }
        // TODO: assuming no actual order, BUT needs positive radii?
        const t = (averageDistance - this.radius0) / (this.radius1 - this.radius0);
        assert && assert(isFinite(t));
        return t;
    }
}
//# sourceMappingURL=RenderRadialBlend.js.map