"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildHumanoidR15Entity = buildHumanoidR15Entity;
const slice_1 = require("../../transforms/voxels/slice");
const materials_1 = require("../../materials");
const entity_utils_1 = require("../../entity-utils");
const common_1 = require("./common");
const REQUIRED_NODES = [
    'Head',
    'Chest',
    'Hip',
    'Right_Thigh',
    'Right_Leg',
    'Right_Foot',
    'Left_Thigh',
    'Left_Leg',
    'Left_Foot',
    'Right_Arm',
    'Right_Forearm',
    'Right_Hand',
    'Left_Arm',
    'Left_Forearm',
    'Left_Hand',
];
const getNode = (nodes, id) => {
    const node = nodes.get(id);
    if (!node) {
        throw new Error(`Humanoid R15 template is missing "${id}" node`);
    }
    return node;
};
const cloneVoxelPrimitive = (source, id) => {
    const primitive = source.primitives.find(entry => entry.id === id);
    if (!primitive || primitive.kind !== 'voxel_box') {
        throw new Error(`Converted humanoid entity is missing voxel primitive "${id}"`);
    }
    return (0, entity_utils_1.cloneEntity)(primitive);
};
const flipVoxelBox = (primitive) => {
    const clone = (0, entity_utils_1.cloneEntity)(primitive);
    const width = Math.max((0, common_1.roundSize)(primitive.size[0]), 1);
    const height = Math.max((0, common_1.roundSize)(primitive.size[1]), 1);
    const depth = Math.max((0, common_1.roundSize)(primitive.size[2]), 1);
    const sourceVoxels = primitive.voxels ?? [];
    const flippedVoxels = new Array(width * height * depth).fill(0);
    for (let z = 0; z < depth; z += 1) {
        for (let y = 0; y < height; y += 1) {
            for (let x = 0; x < width; x += 1) {
                const sourceIndex = x + width * (y + height * z);
                const mirroredX = width - 1 - x;
                const targetIndex = mirroredX + width * (y + height * z);
                flippedVoxels[targetIndex] = sourceVoxels[sourceIndex] ?? 0;
            }
        }
    }
    clone.voxels = flippedVoxels;
    return clone;
};
function buildHumanoidR15Entity(options) {
    const { template, source, outputId } = options;
    const templateClone = (0, common_1.cloneTemplate)(template, outputId);
    const nodesById = (0, common_1.collectNodes)(templateClone.geometry.root, (node) => {
        node.mesh = undefined;
    });
    const headNode = getNode(nodesById, 'Head');
    const chestNode = getNode(nodesById, 'Chest');
    const hipNode = getNode(nodesById, 'Hip');
    const rightThighNode = getNode(nodesById, 'Right_Thigh');
    const rightLegNode = getNode(nodesById, 'Right_Leg');
    const rightFootNode = getNode(nodesById, 'Right_Foot');
    const leftThighNode = getNode(nodesById, 'Left_Thigh');
    const leftLegNode = getNode(nodesById, 'Left_Leg');
    const leftFootNode = getNode(nodesById, 'Left_Foot');
    const rightArmNode = getNode(nodesById, 'Right_Arm');
    const rightForearmNode = getNode(nodesById, 'Right_Forearm');
    const rightHandNode = getNode(nodesById, 'Right_Hand');
    const leftArmNode = getNode(nodesById, 'Left_Arm');
    const leftForearmNode = getNode(nodesById, 'Left_Forearm');
    const leftHandNode = getNode(nodesById, 'Left_Hand');
    const hipOriginalSize = [
        Number(hipNode.size?.[0] ?? 0),
        Number(hipNode.size?.[1] ?? 0),
        Number(hipNode.size?.[2] ?? 0),
    ];
    const chestOriginalSize = [
        Number(chestNode.size?.[0] ?? 0),
        Number(chestNode.size?.[1] ?? 0),
        Number(chestNode.size?.[2] ?? 0),
    ];
    const hipOriginalCenter = [
        Number(hipNode.center?.[0] ?? 0),
        Number(hipNode.center?.[1] ?? (hipOriginalSize[1] || 0) / 2),
        Number(hipNode.center?.[2] ?? 0),
    ];
    const chestOriginalCenter = [
        Number(chestNode.center?.[0] ?? 0),
        Number(chestNode.center?.[1] ?? (chestOriginalSize[1] || 0) / 2),
        Number(chestNode.center?.[2] ?? 0),
    ];
    const hipCenterYOffset = hipOriginalCenter[1] - hipOriginalSize[1] / 2;
    const chestCenterYOffset = chestOriginalCenter[1] - chestOriginalSize[1] / 2;
    const headPrimitive = cloneVoxelPrimitive(source, 'head_primitive');
    headPrimitive.id = `${headNode.id}_voxel`;
    const torsoPrimitive = cloneVoxelPrimitive(source, 'torso_primitive');
    const hipTarget = (0, common_1.roundSize)(hipNode.size?.[1] ?? 6);
    const chestTarget = (0, common_1.roundSize)(chestNode.size?.[1] ?? 20);
    const targetTotal = Math.max(hipTarget + chestTarget, 1);
    const torsoHeight = (0, common_1.roundSize)(torsoPrimitive.size[1]);
    const idealSplit = Math.round((hipTarget / targetTotal) * torsoHeight);
    const splitPlane = Math.min(Math.max(idealSplit, 1), torsoHeight - 1);
    const torsoSplit = (0, slice_1.splitVoxelBoxClone)(torsoPrimitive, 'y', splitPlane);
    const hipPrimitive = torsoSplit.first;
    hipPrimitive.id = `${hipNode.id}_voxel`;
    const chestPrimitive = torsoSplit.second;
    chestPrimitive.id = `${chestNode.id}_voxel`;
    const newPrimitives = [];
    newPrimitives.push(headPrimitive, chestPrimitive, hipPrimitive);
    headNode.mesh = headPrimitive.id;
    hipNode.mesh = hipPrimitive.id;
    chestNode.mesh = chestPrimitive.id;
    hipNode.size = [hipPrimitive.size[0], hipPrimitive.size[1], hipPrimitive.size[2]];
    hipNode.center = [
        hipOriginalCenter[0],
        hipCenterYOffset + hipPrimitive.size[1] / 2,
        hipOriginalCenter[2],
    ];
    chestNode.size = [chestPrimitive.size[0], chestPrimitive.size[1], chestPrimitive.size[2]];
    const chestCenter = [
        chestOriginalCenter[0],
        chestPrimitive.size[1] / 2,
        chestOriginalCenter[2],
    ];
    chestNode.center = chestCenter;
    const chestPosition = [...(chestNode.position ?? [0, 0, 0])];
    chestPosition[1] = hipPrimitive.size[1];
    chestNode.position = chestPosition;
    const chestSizeDelta = chestPrimitive.size[1] - chestOriginalSize[1];
    if (Math.abs(chestSizeDelta) > 1e-6 && Array.isArray(chestNode.children)) {
        chestNode.children.forEach((child) => {
            if (!Array.isArray(child.position)) {
                return;
            }
            const updatedPosition = [...child.position];
            updatedPosition[1] += chestSizeDelta;
            child.position = updatedPosition;
        });
    }
    if (Array.isArray(headNode.position)) {
        const headPosition = [...headNode.position];
        headPosition[1] = chestPrimitive.size[1];
        headNode.position = headPosition;
    }
    const legConfigs = [
        { primitiveId: 'leg_right_primitive', thighNode: rightThighNode, legNode: rightLegNode, footNode: rightFootNode },
        { primitiveId: 'leg_left_primitive', thighNode: leftThighNode, legNode: leftLegNode, footNode: leftFootNode },
    ];
    legConfigs.forEach(config => {
        const sourcePrimitive = cloneVoxelPrimitive(source, config.primitiveId);
        const totalHeight = (0, common_1.roundSize)(sourcePrimitive.size[1]);
        const thighOriginalSize = [
            Number(config.thighNode.size?.[0] ?? 0),
            Number(config.thighNode.size?.[1] ?? 0),
            Number(config.thighNode.size?.[2] ?? 0),
        ];
        const thighOriginalCenter = [
            Number(config.thighNode.center?.[0] ?? 0),
            Number(config.thighNode.center?.[1] ?? 0),
            Number(config.thighNode.center?.[2] ?? 0),
        ];
        const legOriginalSize = [
            Number(config.legNode.size?.[0] ?? 0),
            Number(config.legNode.size?.[1] ?? 0),
            Number(config.legNode.size?.[2] ?? 0),
        ];
        const legOriginalCenter = [
            Number(config.legNode.center?.[0] ?? 0),
            Number(config.legNode.center?.[1] ?? 0),
            Number(config.legNode.center?.[2] ?? 0),
        ];
        const footOriginalSize = [
            Number(config.footNode.size?.[0] ?? 0),
            Number(config.footNode.size?.[1] ?? 0),
            Number(config.footNode.size?.[2] ?? 0),
        ];
        const footOriginalCenter = [
            Number(config.footNode.center?.[0] ?? 0),
            Number(config.footNode.center?.[1] ?? 0),
            Number(config.footNode.center?.[2] ?? 0),
        ];
        const targetThighHeight = (0, common_1.roundSize)(thighOriginalSize[1]);
        const targetLegHeight = (0, common_1.roundSize)(legOriginalSize[1]);
        const targetFootHeight = (0, common_1.roundSize)(footOriginalSize[1]);
        const targetTotalHeight = Math.max(targetThighHeight + targetLegHeight + targetFootHeight, 1);
        let thighHeight = Math.max(1, Math.round((targetThighHeight / targetTotalHeight) * totalHeight));
        let legHeight = Math.max(1, Math.round((targetLegHeight / targetTotalHeight) * totalHeight));
        let footHeight = Math.max(1, totalHeight - thighHeight - legHeight);
        const heightSum = thighHeight + legHeight + footHeight;
        if (heightSum < totalHeight) {
            footHeight += totalHeight - heightSum;
        }
        else if (heightSum > totalHeight) {
            let diff = heightSum - totalHeight;
            const reducers = [
                () => {
                    if (footHeight > 1) {
                        footHeight -= 1;
                        return true;
                    }
                    return false;
                },
                () => {
                    if (legHeight > 1) {
                        legHeight -= 1;
                        return true;
                    }
                    return false;
                },
                () => {
                    if (thighHeight > 1) {
                        thighHeight -= 1;
                        return true;
                    }
                    return false;
                },
            ];
            while (diff > 0) {
                let reduced = false;
                for (const reducer of reducers) {
                    if (reducer()) {
                        diff -= 1;
                        reduced = true;
                        if (diff === 0) {
                            break;
                        }
                    }
                }
                if (!reduced) {
                    break;
                }
            }
        }
        if (thighHeight + legHeight + footHeight !== totalHeight) {
            footHeight = Math.max(1, totalHeight - thighHeight - legHeight);
        }
        const footSplit = (0, slice_1.splitVoxelBoxClone)(sourcePrimitive, 'y', footHeight);
        const footPrimitive = footSplit.first;
        footPrimitive.id = `${config.footNode.id}_voxel`;
        const upperPrimitive = footSplit.second;
        const legSplit = (0, slice_1.splitVoxelBoxClone)(upperPrimitive, 'y', legHeight);
        const legPrimitive = legSplit.first;
        legPrimitive.id = `${config.legNode.id}_voxel`;
        const thighPrimitive = legSplit.second;
        thighPrimitive.id = `${config.thighNode.id}_voxel`;
        config.thighNode.mesh = thighPrimitive.id;
        config.legNode.mesh = legPrimitive.id;
        config.footNode.mesh = footPrimitive.id;
        config.thighNode.size = [thighPrimitive.size[0], thighPrimitive.size[1], thighPrimitive.size[2]];
        (0, entity_utils_1.applyNodeDimensionsAxis)(config.thighNode, thighPrimitive, thighOriginalSize, thighOriginalCenter, 'y');
        config.legNode.size = [legPrimitive.size[0], legPrimitive.size[1], legPrimitive.size[2]];
        (0, entity_utils_1.applyNodeDimensionsAxis)(config.legNode, legPrimitive, legOriginalSize, legOriginalCenter, 'y');
        const legCenter = [
            Number(config.legNode.center?.[0] ?? 0),
            -legPrimitive.size[1] / 2,
            Number(config.legNode.center?.[2] ?? 0),
        ];
        config.legNode.center = legCenter;
        config.footNode.size = [footPrimitive.size[0], footPrimitive.size[1], footPrimitive.size[2]];
        (0, entity_utils_1.applyNodeDimensionsAxis)(config.footNode, footPrimitive, footOriginalSize, footOriginalCenter, 'y');
        const thighBottom = (config.thighNode.center?.[1] ?? 0) - thighPrimitive.size[1] / 2;
        (0, entity_utils_1.updateChildPositionAxis)(config.thighNode, config.legNode.id, 'y', thighBottom);
        const legBottom = (config.legNode.center?.[1] ?? 0) - legPrimitive.size[1] / 2;
        (0, entity_utils_1.updateChildPositionAxis)(config.legNode, config.footNode.id, 'y', legBottom);
        const thighSizeDelta = thighPrimitive.size[1] - thighOriginalSize[1];
        if (Math.abs(thighSizeDelta) > 1e-6 && Array.isArray(config.thighNode.children)) {
            config.thighNode.children.forEach(child => {
                if (child.id === config.legNode.id || !Array.isArray(child.position)) {
                    return;
                }
                const updated = [...child.position];
                updated[1] += thighSizeDelta;
                child.position = updated;
            });
        }
        const legSizeDelta = legPrimitive.size[1] - legOriginalSize[1];
        if (Math.abs(legSizeDelta) > 1e-6 && Array.isArray(config.legNode.children)) {
            config.legNode.children.forEach(child => {
                if (child.id === config.footNode.id || !Array.isArray(child.position)) {
                    return;
                }
                const updated = [...child.position];
                updated[1] += legSizeDelta;
                child.position = updated;
            });
        }
        newPrimitives.push(thighPrimitive, legPrimitive, footPrimitive);
    });
    const applyArmDimensions = (node, primitive, originalSize, originalCenter, orientation) => {
        node.size = [primitive.size[0], primitive.size[1], primitive.size[2]];
        const center = [
            Number(originalCenter[0]),
            Number(originalCenter[1]),
            Number(originalCenter[2]),
        ];
        center[0] = (0, entity_utils_1.anchorCenter)(Number(originalCenter[0]), Number(originalSize[0]), primitive.size[0], orientation);
        node.center = center;
    };
    const armConfigs = [
        {
            primitiveId: 'arm_left_primitive',
            armNode: leftArmNode,
            forearmNode: leftForearmNode,
            handNode: leftHandNode,
            orientation: 1,
        },
        {
            primitiveId: 'arm_right_primitive',
            armNode: rightArmNode,
            forearmNode: rightForearmNode,
            handNode: rightHandNode,
            orientation: -1,
        },
    ];
    armConfigs.forEach(config => {
        const basePrimitive = cloneVoxelPrimitive(source, config.primitiveId);
        const orientedPrimitive = config.orientation === -1 ? flipVoxelBox(basePrimitive) : basePrimitive;
        const totalWidth = (0, common_1.roundSize)(orientedPrimitive.size[0]);
        const armOriginalSize = [
            Number(config.armNode.size?.[0] ?? 0),
            Number(config.armNode.size?.[1] ?? 0),
            Number(config.armNode.size?.[2] ?? 0),
        ];
        const armOriginalCenter = [
            Number(config.armNode.center?.[0] ?? 0),
            Number(config.armNode.center?.[1] ?? 0),
            Number(config.armNode.center?.[2] ?? 0),
        ];
        const forearmOriginalSize = [
            Number(config.forearmNode.size?.[0] ?? 0),
            Number(config.forearmNode.size?.[1] ?? 0),
            Number(config.forearmNode.size?.[2] ?? 0),
        ];
        const forearmOriginalCenter = [
            Number(config.forearmNode.center?.[0] ?? 0),
            Number(config.forearmNode.center?.[1] ?? 0),
            Number(config.forearmNode.center?.[2] ?? 0),
        ];
        const handOriginalSize = [
            Number(config.handNode.size?.[0] ?? 0),
            Number(config.handNode.size?.[1] ?? 0),
            Number(config.handNode.size?.[2] ?? 0),
        ];
        const handOriginalCenter = [
            Number(config.handNode.center?.[0] ?? 0),
            Number(config.handNode.center?.[1] ?? 0),
            Number(config.handNode.center?.[2] ?? 0),
        ];
        const targetArmWidth = Math.min(Math.max((0, common_1.roundSize)(armOriginalSize[0]), 1), totalWidth - 2);
        const remainderAfterArm = Math.max(totalWidth - targetArmWidth, 2);
        const targetForearmWidth = Math.min(Math.max((0, common_1.roundSize)(forearmOriginalSize[0]), 1), remainderAfterArm - 1);
        const targetHandWidth = Math.max(Math.min((0, common_1.roundSize)(handOriginalSize[0]), remainderAfterArm - targetForearmWidth), 1);
        const armSplit = (0, slice_1.splitVoxelBoxClone)(orientedPrimitive, 'x', targetArmWidth);
        const forearmSplit = (0, slice_1.splitVoxelBoxClone)(armSplit.second, 'x', targetForearmWidth);
        const orientBack = (primitive) => {
            return config.orientation === -1 ? flipVoxelBox(primitive) : primitive;
        };
        const armPrimitive = orientBack(armSplit.first);
        const forearmPrimitive = orientBack(forearmSplit.first);
        const handPrimitive = orientBack(forearmSplit.second);
        armPrimitive.id = `${config.armNode.id}_voxel`;
        forearmPrimitive.id = `${config.forearmNode.id}_voxel`;
        handPrimitive.id = `${config.handNode.id}_voxel`;
        config.armNode.mesh = armPrimitive.id;
        config.forearmNode.mesh = forearmPrimitive.id;
        config.handNode.mesh = handPrimitive.id;
        applyArmDimensions(config.armNode, armPrimitive, armOriginalSize, armOriginalCenter, config.orientation);
        applyArmDimensions(config.forearmNode, forearmPrimitive, forearmOriginalSize, forearmOriginalCenter, config.orientation);
        applyArmDimensions(config.handNode, handPrimitive, handOriginalSize, handOriginalCenter, config.orientation);
        newPrimitives.push(armPrimitive, forearmPrimitive, handPrimitive);
    });
    const paletteIds = (0, materials_1.collectPaletteIds)(newPrimitives);
    const materials = (0, materials_1.cloneMaterialsByPalette)(source.materials, paletteIds);
    const materialIds = new Set(materials.map(material => material.id));
    source.materials?.forEach(material => {
        if (materialIds.has(material.id)) {
            return;
        }
        materials.push(JSON.parse(JSON.stringify(material)));
        materialIds.add(material.id);
    });
    templateClone.primitives = newPrimitives;
    templateClone.materials = materials;
    templateClone.patterns = source.patterns;
    templateClone.textures = source.textures;
    return templateClone;
}
//# sourceMappingURL=voxel-builder.js.map