import '../../../assert/js/assert.js';
import '../../../globals.js';
import Vector2 from '../../../dot/js/Vector2.js';
import Vector3 from '../../../dot/js/Vector3.js';
import Vector4 from '../../../dot/js/Vector4.js';
import { alpenglow, RenderColor, RenderInstruction, RenderProgram } from '../imports.js';
export var RenderBarycentricPerspectiveBlendAccuracy;
(function (RenderBarycentricPerspectiveBlendAccuracy) {
    // TODO: Accurate should really be the version that runs the perspective correction integral!!!!
    // TODO: do this, the math should work!
    RenderBarycentricPerspectiveBlendAccuracy[RenderBarycentricPerspectiveBlendAccuracy["Centroid"] = 0] = "Centroid";
    RenderBarycentricPerspectiveBlendAccuracy[RenderBarycentricPerspectiveBlendAccuracy["PixelCenter"] = 1] = "PixelCenter";
})(RenderBarycentricPerspectiveBlendAccuracy || (RenderBarycentricPerspectiveBlendAccuracy = {}));
const scratchCentroid = new Vector2(0, 0);
alpenglow.register('RenderBarycentricPerspectiveBlendAccuracy', RenderBarycentricPerspectiveBlendAccuracy);
export default class RenderBarycentricPerspectiveBlend extends RenderProgram {
    constructor(pointA, pointB, pointC, accuracy, a, b, c, logic) {
        assert && assert(pointA.isFinite());
        assert && assert(pointB.isFinite());
        assert && assert(pointC.isFinite());
        assert && assert(!pointA.toVector2().equals(pointB.toVector2()));
        assert && assert(!pointB.toVector2().equals(pointC.toVector2()));
        assert && assert(!pointC.toVector2().equals(pointA.toVector2()));
        assert && assert(pointA.z > 0 && pointB.z > 0 && pointC.z > 0, 'All points must be in front of the camera');
        super([a, b, c], a.isFullyTransparent && b.isFullyTransparent && c.isFullyTransparent, a.isFullyOpaque && b.isFullyOpaque && c.isFullyOpaque, false, false, accuracy === RenderBarycentricPerspectiveBlendAccuracy.Centroid);
        this.pointA = pointA;
        this.pointB = pointB;
        this.pointC = pointC;
        this.accuracy = accuracy;
        this.a = a;
        this.b = b;
        this.c = c;
        this.logic = logic || RenderBarycentricPerspectiveBlendLogic.from(this.pointA, this.pointB, this.pointC, this.accuracy);
    }
    getName() {
        return 'RenderBarycentricPerspectiveBlend';
    }
    withChildren(children) {
        assert && assert(children.length === 3);
        return new RenderBarycentricPerspectiveBlend(this.pointA, this.pointB, this.pointC, this.accuracy, children[0], children[1], children[2], this.logic);
    }
    transformed(transform) {
        const xyA = transform.timesVector2(this.pointA.toVector2());
        const xyB = transform.timesVector2(this.pointB.toVector2());
        const xyC = transform.timesVector2(this.pointC.toVector2());
        return new RenderBarycentricPerspectiveBlend(new Vector3(xyA.x, xyA.y, this.pointA.z), new Vector3(xyB.x, xyB.y, this.pointB.z), new Vector3(xyC.x, xyC.y, this.pointC.z), this.accuracy, this.a.transformed(transform), this.b.transformed(transform), this.c.transformed(transform));
    }
    equalsTyped(other) {
        return this.pointA.equals(other.pointA) &&
            this.pointB.equals(other.pointB) &&
            this.pointC.equals(other.pointC) &&
            this.accuracy === other.accuracy;
    }
    // TODO: extract code for the barycentric blends? duplicated
    getSimplified(children) {
        const a = children[0];
        const b = children[1];
        const c = children[2];
        if (a.isFullyTransparent && b.isFullyTransparent && c.isFullyTransparent) {
            return RenderColor.TRANSPARENT;
        }
        else if (a.equals(b) && a.equals(c)) {
            return a;
        }
        else {
            return null;
        }
    }
    evaluate(context) {
        const aColor = this.a.evaluate(context);
        const bColor = this.b.evaluate(context);
        const cColor = this.c.evaluate(context);
        const vector = new Vector4(0, 0, 0, 0);
        this.logic.apply(vector, context, aColor, bColor, cColor);
        return vector;
    }
    writeInstructions(instructions) {
        this.c.writeInstructions(instructions);
        this.b.writeInstructions(instructions);
        this.a.writeInstructions(instructions);
        instructions.push(new RenderInstructionBarycentricPerspectiveBlend(this.logic));
    }
    serialize() {
        return {
            type: 'RenderBarycentricPerspectiveBlend',
            pointA: [this.pointA.x, this.pointA.y, this.pointA.z],
            pointB: [this.pointB.x, this.pointB.y, this.pointB.z],
            pointC: [this.pointC.x, this.pointC.y, this.pointC.z],
            accuracy: this.accuracy,
            a: this.a.serialize(),
            b: this.b.serialize(),
            c: this.c.serialize()
        };
    }
    static deserialize(obj) {
        return new RenderBarycentricPerspectiveBlend(new Vector3(obj.pointA[0], obj.pointA[1], obj.pointA[2]), new Vector3(obj.pointB[0], obj.pointB[1], obj.pointB[2]), new Vector3(obj.pointC[0], obj.pointC[1], obj.pointC[2]), obj.accuracy, RenderProgram.deserialize(obj.a), RenderProgram.deserialize(obj.b), RenderProgram.deserialize(obj.c));
    }
}
alpenglow.register('RenderBarycentricPerspectiveBlend', RenderBarycentricPerspectiveBlend);
export class RenderBarycentricPerspectiveBlendLogic {
    constructor(det, diffA, diffB, point2C, zInverseA, zInverseB, zInverseC, accuracy) {
        this.det = det;
        this.diffA = diffA;
        this.diffB = diffB;
        this.point2C = point2C;
        this.zInverseA = zInverseA;
        this.zInverseB = zInverseB;
        this.zInverseC = zInverseC;
        this.accuracy = accuracy;
    }
    static from(pointA, pointB, pointC, accuracy) {
        const pA = pointA;
        const pB = pointB;
        const pC = pointC;
        assert && assert(pointA.z !== 0);
        assert && assert(pointB.z !== 0);
        assert && assert(pointC.z !== 0);
        const det = (pB.y - pC.y) * (pA.x - pC.x) + (pC.x - pB.x) * (pA.y - pC.y);
        const diffA = new Vector2(pB.y - pC.y, pC.x - pB.x);
        const diffB = new Vector2(pC.y - pA.y, pA.x - pC.x);
        const point2C = pC.toVector2();
        const zInverseA = 1 / pA.z;
        const zInverseB = 1 / pB.z;
        const zInverseC = 1 / pC.z;
        /*
        NOTES FOR THE FUTURE: Here were the original formulas
        const det = ( pB.y - pC.y ) * ( pA.x - pC.x ) + ( pC.x - pB.x ) * ( pA.y - pC.y );
        const lambdaA = ( ( pB.y - pC.y ) * ( point.x - pC.x ) + ( pC.x - pB.x ) * ( point.y - pC.y ) ) / det;
        const lambdaB = ( ( pC.y - pA.y ) * ( point.x - pC.x ) + ( pA.x - pC.x ) * ( point.y - pC.y ) ) / det;
         */
        return new RenderBarycentricPerspectiveBlendLogic(det, diffA, diffB, point2C, zInverseA, zInverseB, zInverseC, accuracy);
    }
    equals(other) {
        return Math.abs(this.det - other.det) < 1e-6 &&
            this.diffA.equalsEpsilon(other.diffA, 1e-6) &&
            this.diffB.equalsEpsilon(other.diffB, 1e-6) &&
            this.point2C.equalsEpsilon(other.point2C, 1e-6) &&
            Math.abs(this.zInverseA - other.zInverseA) < 1e-6 &&
            Math.abs(this.zInverseB - other.zInverseB) < 1e-6 &&
            Math.abs(this.zInverseC - other.zInverseC) < 1e-6 &&
            this.accuracy === other.accuracy;
    }
    apply(output, context, aColor, bColor, cColor) {
        const point = this.accuracy === RenderBarycentricPerspectiveBlendAccuracy.Centroid ? context.centroid : context.writeBoundsCentroid(scratchCentroid);
        const lambdaA = (this.diffA.x * (point.x - this.point2C.x) + this.diffA.y * (point.y - this.point2C.y)) / this.det;
        const lambdaB = (this.diffB.x * (point.x - this.point2C.x) + this.diffB.y * (point.y - this.point2C.y)) / this.det;
        const lambdaC = 1 - lambdaA - lambdaB;
        assert && assert(isFinite(lambdaA), 'Lambda A must be finite');
        assert && assert(isFinite(lambdaB), 'Lambda B must be finite');
        assert && assert(isFinite(lambdaC), 'Lambda C must be finite');
        assert && assert(isFinite(this.zInverseA));
        assert && assert(isFinite(this.zInverseB));
        assert && assert(isFinite(this.zInverseC));
        const z = 1 / (lambdaA * this.zInverseA + lambdaB * this.zInverseB + lambdaC * this.zInverseC);
        assert && assert(aColor.isFinite(), `Color A must be finite: ${aColor}`);
        assert && assert(bColor.isFinite(), `Color B must be finite: ${bColor}`);
        assert && assert(cColor.isFinite(), `Color C must be finite: ${cColor}`);
        assert && assert(z > 0, 'z must be positive');
        const aFactor = z * lambdaA * this.zInverseA;
        const bFactor = z * lambdaB * this.zInverseB;
        const cFactor = z * lambdaC * this.zInverseC;
        output.setXYZW(aColor.x * aFactor + bColor.x * bFactor + cColor.x * cFactor, aColor.y * aFactor + bColor.y * bFactor + cColor.y * cFactor, aColor.z * aFactor + bColor.z * bFactor + cColor.z * cFactor, aColor.w * aFactor + bColor.w * bFactor + cColor.w * cFactor);
    }
}
const scratchAColor = new Vector4(0, 0, 0, 0);
const scratchBColor = new Vector4(0, 0, 0, 0);
const scratchCColor = new Vector4(0, 0, 0, 0);
const scratchResult = new Vector4(0, 0, 0, 0);
export class RenderInstructionBarycentricPerspectiveBlend extends RenderInstruction {
    constructor(logic) {
        super();
        this.logic = logic;
    }
    toString() {
        const det = `det:${this.logic.det}`;
        const diffA = `diffA:${this.logic.diffA.x},${this.logic.diffA.y}`;
        const diffB = `diffB:${this.logic.diffB.x},${this.logic.diffB.y}`;
        const pointC = `pointC:${this.logic.point2C.x},${this.logic.point2C.y}`;
        const zValues = `zInverses:${this.logic.zInverseA},${this.logic.zInverseB},${this.logic.zInverseC}`;
        return `RenderInstructionBarycentricPerspectiveBlend(${det} ${diffA} ${diffB} ${pointC} ${zValues})`;
    }
    equals(other, areLocationsEqual) {
        return other instanceof RenderInstructionBarycentricPerspectiveBlend && this.logic.equals(other.logic);
    }
    execute(stack, context, executor) {
        const aColor = stack.popInto(scratchAColor);
        const bColor = stack.popInto(scratchBColor);
        const cColor = stack.popInto(scratchCColor);
        this.logic.apply(scratchResult, context, aColor, bColor, cColor);
        stack.push(scratchResult);
    }
    writeBinary(encoder, getOffset) {
        encoder.pushU32(RenderInstruction.BarycentricPerspectiveBlendCode | (this.logic.accuracy << 8)); // 0
        encoder.pushF32(this.logic.det); // 1
        encoder.pushF32(this.logic.diffA.x); // 2
        encoder.pushF32(this.logic.diffA.y); // 3
        encoder.pushF32(this.logic.diffB.x); // 4
        encoder.pushF32(this.logic.diffB.y); // 5
        encoder.pushF32(this.logic.point2C.x); // 6
        encoder.pushF32(this.logic.point2C.y); // 7
        encoder.pushF32(this.logic.zInverseA); // 8
        encoder.pushF32(this.logic.zInverseB); // 9
        encoder.pushF32(this.logic.zInverseC); // 10
    }
    static fromBinary(encoder, offset, getLocation) {
        const accuracy = (encoder.fullU32Array[offset] >> 8) & 0xff;
        const det = encoder.fullF32Array[offset + 1];
        const diffA = new Vector2(encoder.fullF32Array[offset + 2], encoder.fullF32Array[offset + 3]);
        const diffB = new Vector2(encoder.fullF32Array[offset + 4], encoder.fullF32Array[offset + 5]);
        const point2C = new Vector2(encoder.fullF32Array[offset + 6], encoder.fullF32Array[offset + 7]);
        const zInverseA = encoder.fullF32Array[offset + 8];
        const zInverseB = encoder.fullF32Array[offset + 9];
        const zInverseC = encoder.fullF32Array[offset + 10];
        return new RenderInstructionBarycentricPerspectiveBlend(new RenderBarycentricPerspectiveBlendLogic(det, diffA, diffB, point2C, zInverseA, zInverseB, zInverseC, accuracy));
    }
    getBinaryLength() {
        return 11;
    }
}
//# sourceMappingURL=RenderBarycentricPerspectiveBlend.js.map