"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertValidVoxBuffer = assertValidVoxBuffer;
exports.extractVoxChunks = extractVoxChunks;
exports.parseVoxChunks = parseVoxChunks;
exports.convertVoxChunksToBoxel = convertVoxChunksToBoxel;
exports.convertVoxToBoxel = convertVoxToBoxel;
exports.buildVoxGroupFromChunks = buildVoxGroupFromChunks;
exports.buildVoxGroup = buildVoxGroup;
exports.renderVoxToDataURL = renderVoxToDataURL;
const PREVIEW_TARGET_HEIGHT = 1.8;
const PREVIEW_MODEL_LIFT = 0.05;
const PREVIEW_AMBIENT_LIGHT_COLOR = 0xb4c6fc;
const PREVIEW_AMBIENT_LIGHT_INTENSITY = 0.55;
const PREVIEW_DIRECTIONAL_LIGHT_COLOR = 0xf8fafc;
const PREVIEW_DIRECTIONAL_LIGHT_INTENSITY = 0.9;
const PREVIEW_DIRECTIONAL_LIGHT_MIN_HEIGHT = 1.6;
const PREVIEW_BACKGROUND_COLOR = 0x111827;
const PREVIEW_FLOOR_COLOR_1 = 0x1c7c3c;
const PREVIEW_FLOOR_COLOR_2 = 0x14532d;
const PREVIEW_GROUND_SIZE = 20;
const PREVIEW_TILE_SIZE = 1;
const PREVIEW_CAMERA_DIRECTION = [-0.65, 0.6, 1];
const PREVIEW_CAMERA_MIN_DISTANCE = 1.8;
const PREVIEW_CAMERA_MIN_TARGET_Y = 0.2;
const PREVIEW_SHADOW_MAP_SIZE = 2048;
const PREVIEW_FOG_NEAR = 8;
const PREVIEW_FOG_FAR = 35;
function toArrayBuffer(input) {
    if (input instanceof ArrayBuffer) {
        return input;
    }
    const view = input;
    const copy = new Uint8Array(view.byteLength);
    copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
    return copy.buffer;
}
function assertValidVoxBuffer(input) {
    const buffer = toArrayBuffer(input);
    if (buffer.byteLength < 8) {
        throw new Error('vox_buffer_too_small');
    }
    const view = new DataView(buffer);
    const magic = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
    if (magic !== 'VOX ') {
        throw new Error('vox_header_invalid');
    }
    const version = view.getUint32(4, true);
    if (version !== 150) {
        throw new Error(`vox_version_unsupported:${version}`);
    }
    return buffer;
}
async function loadVoxLoaderModule() {
    return (await Promise.resolve().then(() => __importStar(require('three/examples/jsm/loaders/VOXLoader.js'))));
}
function extractVoxChunks(result) {
    if (Array.isArray(result) && result.length > 0) {
        return result;
    }
    if (result && typeof result === 'object') {
        const chunks = result.chunks;
        if (Array.isArray(chunks) && chunks.length > 0) {
            return chunks;
        }
    }
    throw new Error('vox_chunks_missing');
}
async function parseVoxChunks(input) {
    const buffer = assertValidVoxBuffer(input);
    const loaderModule = await loadVoxLoaderModule();
    const loader = new loaderModule.VOXLoader();
    return extractVoxChunks(loader.parse(buffer));
}
function paletteHexToMaterial(hex, id) {
    const red = hex & 0xff;
    const green = (hex >> 8) & 0xff;
    const blue = (hex >> 16) & 0xff;
    const alpha = (hex >> 24) & 0xff;
    const material = {
        id,
        baseColor: [red, green, blue],
    };
    if (alpha >= 0 && alpha < 255) {
        material.opacity = Number((alpha / 255).toFixed(4));
    }
    return material;
}
function buildPrimitiveFromChunk(chunk, index, materialsByHex, materials) {
    const size = [chunk.size.x, chunk.size.y, chunk.size.z];
    const total = size[0] * size[1] * size[2];
    const voxels = new Array(total).fill(0);
    const paletteColorIndices = new Set();
    for (let offset = 0; offset < chunk.data.length; offset += 4) {
        paletteColorIndices.add(chunk.data[offset + 3] ?? 0);
    }
    const sortedColorIndices = Array.from(paletteColorIndices).filter(value => value > 0).sort((left, right) => left - right);
    const paletteLookup = new Map();
    const palette = [];
    sortedColorIndices.forEach((colorIndex, paletteIndex) => {
        const hex = chunk.palette[colorIndex] ?? 0xffffffff;
        let materialId = materialsByHex.get(hex);
        if (!materialId) {
            materialId = `vox_material_${hex.toString(16).padStart(8, '0')}`;
            materialsByHex.set(hex, materialId);
            materials.push(paletteHexToMaterial(hex, materialId));
        }
        palette.push(materialId);
        paletteLookup.set(colorIndex, paletteIndex);
    });
    for (let offset = 0; offset < chunk.data.length; offset += 4) {
        const x = chunk.data[offset] ?? 0;
        const y = chunk.data[offset + 1] ?? 0;
        const z = chunk.data[offset + 2] ?? 0;
        const colorIndex = chunk.data[offset + 3] ?? 0;
        const paletteIndex = paletteLookup.get(colorIndex);
        if (paletteIndex === undefined) {
            continue;
        }
        const voxelIndex = x + y * size[0] + z * size[0] * size[1];
        voxels[voxelIndex] = paletteIndex + 1;
    }
    const primitiveId = `vox_primitive_${index + 1}`;
    return {
        primitive: {
            id: primitiveId,
            kind: 'voxel_box',
            size,
            voxels,
            palette,
        },
        node: {
            id: `vox_node_${index + 1}`,
            position: [0, 0, 0],
            size,
            mesh: primitiveId,
            children: [],
        },
    };
}
function convertVoxChunksToBoxel(chunks, id = 'vox_boxel') {
    if (!Array.isArray(chunks) || chunks.length === 0) {
        throw new Error('vox_chunks_required');
    }
    const materials = [];
    const materialsByHex = new Map();
    const primitives = [];
    const nodes = [];
    chunks.forEach((chunk, index) => {
        const { primitive, node } = buildPrimitiveFromChunk(chunk, index, materialsByHex, materials);
        primitives.push(primitive);
        nodes.push(node);
    });
    const rootNode = nodes.length === 1
        ? {
            ...nodes[0],
            id: `${id}_root`,
        }
        : {
            id: `${id}_root`,
            position: [0, 0, 0],
            children: nodes,
        };
    return {
        id,
        geometry: {
            id: `${id}_geometry`,
            root: rootNode,
        },
        patterns: [],
        materials,
        textures: [],
        primitives,
        animations: [],
    };
}
async function convertVoxToBoxel(input, id) {
    const chunks = await parseVoxChunks(input);
    return convertVoxChunksToBoxel(chunks, id);
}
const FACE_NEGATIVE_X = [0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1];
const FACE_POSITIVE_X = [1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0];
const FACE_POSITIVE_Y = [0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1];
const FACE_NEGATIVE_Y = [0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0];
const FACE_NEGATIVE_Z = [0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0];
const FACE_POSITIVE_Z = [0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1];
function pushFace(three, vertices, colors, tile, chunk, x, y, z, red, green, blue) {
    const color = new three.Color();
    const srgbColorSpace = three.SRGBColorSpace;
    const translatedX = x - chunk.size.x / 2;
    const translatedY = y - chunk.size.z / 2;
    const translatedZ = z + chunk.size.y / 2;
    if (srgbColorSpace) {
        color.setRGB(red, green, blue, srgbColorSpace);
    }
    else {
        color.setRGB(red, green, blue);
    }
    for (let index = 0; index < tile.length; index += 3) {
        vertices.push(tile[index] + translatedX, tile[index + 1] + translatedY, tile[index + 2] + translatedZ);
        colors.push(color.r, color.g, color.b);
    }
}
function buildVoxMeshFromChunk(three, chunk) {
    const vertices = [];
    const colors = [];
    const occupancy = new Uint8Array(chunk.size.x * chunk.size.y * chunk.size.z);
    const offsetY = chunk.size.x;
    const offsetZ = chunk.size.x * chunk.size.y;
    for (let index = 0; index < chunk.data.length; index += 4) {
        const x = chunk.data[index] ?? 0;
        const y = chunk.data[index + 1] ?? 0;
        const z = chunk.data[index + 2] ?? 0;
        const voxelIndex = x + y * offsetY + z * offsetZ;
        occupancy[voxelIndex] = 255;
    }
    let hasColors = false;
    for (let index = 0; index < chunk.data.length; index += 4) {
        const x = chunk.data[index] ?? 0;
        const y = chunk.data[index + 1] ?? 0;
        const z = chunk.data[index + 2] ?? 0;
        const colorIndex = chunk.data[index + 3] ?? 0;
        const hex = chunk.palette[colorIndex] ?? 0;
        const red = (hex & 0xff) / 0xff;
        const green = ((hex >> 8) & 0xff) / 0xff;
        const blue = ((hex >> 16) & 0xff) / 0xff;
        if (red > 0 || green > 0 || blue > 0) {
            hasColors = true;
        }
        const voxelIndex = x + y * offsetY + z * offsetZ;
        if (x === chunk.size.x - 1 || occupancy[voxelIndex + 1] === 0) {
            pushFace(three, vertices, colors, FACE_POSITIVE_X, chunk, x, z, -y, red, green, blue);
        }
        if (x === 0 || occupancy[voxelIndex - 1] === 0) {
            pushFace(three, vertices, colors, FACE_NEGATIVE_X, chunk, x, z, -y, red, green, blue);
        }
        if (y === chunk.size.y - 1 || occupancy[voxelIndex + offsetY] === 0) {
            pushFace(three, vertices, colors, FACE_NEGATIVE_Y, chunk, x, z, -y, red, green, blue);
        }
        if (y === 0 || occupancy[voxelIndex - offsetY] === 0) {
            pushFace(three, vertices, colors, FACE_POSITIVE_Y, chunk, x, z, -y, red, green, blue);
        }
        if (z === chunk.size.z - 1 || occupancy[voxelIndex + offsetZ] === 0) {
            pushFace(three, vertices, colors, FACE_POSITIVE_Z, chunk, x, z, -y, red, green, blue);
        }
        if (z === 0 || occupancy[voxelIndex - offsetZ] === 0) {
            pushFace(three, vertices, colors, FACE_NEGATIVE_Z, chunk, x, z, -y, red, green, blue);
        }
    }
    const geometry = new three.BufferGeometry();
    geometry.setAttribute('position', new three.Float32BufferAttribute(vertices, 3));
    geometry.computeVertexNormals();
    const material = new three.MeshStandardMaterial();
    if (hasColors) {
        geometry.setAttribute('color', new three.Float32BufferAttribute(colors, 3));
        material.vertexColors = true;
    }
    return new three.Mesh(geometry, material);
}
function buildVoxGroupFromChunks(chunks, three) {
    if (!Array.isArray(chunks) || chunks.length === 0) {
        throw new Error('vox_chunks_required');
    }
    const group = new three.Group();
    chunks.forEach((chunk) => {
        group.add(buildVoxMeshFromChunk(three, chunk));
    });
    return group;
}
async function buildVoxGroup(input, three) {
    const chunks = await parseVoxChunks(input);
    const runtimeThree = three ?? (await Promise.resolve().then(() => __importStar(require('three'))));
    return buildVoxGroupFromChunks(chunks, runtimeThree);
}
function setMeshShadowFlags(root) {
    root.traverse((child) => {
        const mesh = child;
        if (mesh.isMesh) {
            mesh.castShadow = true;
            mesh.receiveShadow = true;
        }
    });
}
function disposeSceneObject(root) {
    root.traverse((child) => {
        const mesh = child;
        mesh.geometry?.dispose?.();
        if (Array.isArray(mesh.material)) {
            mesh.material.forEach((material) => material.dispose?.());
        }
        else {
            mesh.material?.dispose?.();
        }
    });
}
function createPreviewGround(three) {
    const group = new three.Group();
    const color1 = new three.Color(PREVIEW_FLOOR_COLOR_1);
    const color2 = new three.Color(PREVIEW_FLOOR_COLOR_2);
    for (let x = -PREVIEW_GROUND_SIZE / 2; x < PREVIEW_GROUND_SIZE / 2; x += 1) {
        for (let z = -PREVIEW_GROUND_SIZE / 2; z < PREVIEW_GROUND_SIZE / 2; z += 1) {
            const material = new three.MeshStandardMaterial({
                color: (x + z) % 2 === 0 ? color1 : color2,
                roughness: 0.9,
                metalness: 0,
            });
            const tile = new three.Mesh(new three.PlaneGeometry(PREVIEW_TILE_SIZE, PREVIEW_TILE_SIZE), material);
            tile.rotation.x = -Math.PI / 2;
            tile.position.set((x + 0.5) * PREVIEW_TILE_SIZE, 0, (z + 0.5) * PREVIEW_TILE_SIZE);
            tile.receiveShadow = true;
            group.add(tile);
        }
    }
    return group;
}
function normalizePreviewPlacement(three, root) {
    const initialBounds = new three.Box3().setFromObject(root);
    const initialSize = initialBounds.getSize(new three.Vector3());
    if (!(initialSize.y > 0) || !Number.isFinite(initialSize.y)) {
        throw new Error('vox_preview_invalid_bounds');
    }
    root.scale.setScalar(PREVIEW_TARGET_HEIGHT / initialSize.y);
    root.updateMatrixWorld(true);
    const scaledBounds = new three.Box3().setFromObject(root);
    const scaledCenter = scaledBounds.getCenter(new three.Vector3());
    const groundOffset = new three.Vector3(scaledCenter.x, scaledBounds.min.y, scaledCenter.z);
    root.position.sub(groundOffset);
    root.position.y += PREVIEW_MODEL_LIFT;
    root.updateMatrixWorld(true);
    return new three.Box3().setFromObject(root).getSize(new three.Vector3());
}
function framePreviewCamera(three, camera, directional, size) {
    const maxDim = Math.max(size.x, size.y, size.z, 0.01);
    const distance = Math.max(maxDim * 1.8 * 1.5, PREVIEW_CAMERA_MIN_DISTANCE * 1.5);
    const direction = new three.Vector3().fromArray(PREVIEW_CAMERA_DIRECTION).normalize();
    const position = direction.multiplyScalar(distance);
    const targetY = Math.max(size.y * 0.5, PREVIEW_CAMERA_MIN_TARGET_Y);
    camera.position.set(position.x, Math.max(position.y, PREVIEW_DIRECTIONAL_LIGHT_MIN_HEIGHT), position.z);
    camera.lookAt(0, targetY, 0);
    camera.near = Math.max(distance / 100, 0.05);
    camera.far = Math.max(distance * 10, 50);
    camera.updateProjectionMatrix();
    directional.position.set(position.x, position.y, position.z);
    directional.target.position.set(0, targetY, 0);
    directional.target.updateMatrixWorld();
}
async function renderVoxToDataURL(options) {
    const { THREE: three, renderer, input, width, height, } = options;
    const sizeTarget = new three.Vector2();
    renderer.getSize(sizeTarget);
    const previousWidth = sizeTarget.x;
    const previousHeight = sizeTarget.y;
    const previousPixelRatio = renderer.getPixelRatio();
    const previousShadowEnabled = renderer.shadowMap.enabled;
    const previousShadowType = renderer.shadowMap.type;
    const previousClearColor = renderer.getClearColor(new three.Color());
    const previousClearAlpha = renderer.getClearAlpha();
    const targetWidth = width ?? (previousWidth || 1024);
    const targetHeight = height ?? (previousHeight || 1024);
    renderer.setPixelRatio(1);
    renderer.setSize(targetWidth, targetHeight, false);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = three.PCFSoftShadowMap;
    renderer.setClearColor(new three.Color(PREVIEW_BACKGROUND_COLOR), 1);
    const scene = new three.Scene();
    scene.fog = new three.Fog(PREVIEW_BACKGROUND_COLOR, PREVIEW_FOG_NEAR, PREVIEW_FOG_FAR);
    const ambient = new three.AmbientLight(PREVIEW_AMBIENT_LIGHT_COLOR, PREVIEW_AMBIENT_LIGHT_INTENSITY);
    const directional = new three.DirectionalLight(PREVIEW_DIRECTIONAL_LIGHT_COLOR, PREVIEW_DIRECTIONAL_LIGHT_INTENSITY);
    directional.castShadow = true;
    directional.shadow.mapSize.set(PREVIEW_SHADOW_MAP_SIZE, PREVIEW_SHADOW_MAP_SIZE);
    const ground = createPreviewGround(three);
    const camera = new three.PerspectiveCamera(50, targetWidth / targetHeight, 0.1, 500);
    const root = await buildVoxGroup(input, three);
    setMeshShadowFlags(root);
    const finalSize = normalizePreviewPlacement(three, root);
    scene.add(ambient);
    scene.add(directional);
    scene.add(directional.target);
    scene.add(ground);
    scene.add(root);
    framePreviewCamera(three, camera, directional, finalSize);
    try {
        renderer.render(scene, camera);
        return renderer.domElement.toDataURL('image/png');
    }
    finally {
        scene.remove(root);
        scene.remove(ground);
        scene.remove(directional.target);
        scene.remove(directional);
        scene.remove(ambient);
        disposeSceneObject(root);
        disposeSceneObject(ground);
        renderer.setPixelRatio(previousPixelRatio);
        renderer.setSize(previousWidth || targetWidth, previousHeight || targetHeight, false);
        renderer.shadowMap.enabled = previousShadowEnabled;
        renderer.shadowMap.type = previousShadowType;
        renderer.setClearColor(previousClearColor, previousClearAlpha);
    }
}
//# sourceMappingURL=vox.js.map