/***********************************************/
/*           Copyright (c) 2025 Belmu          */
/*             All Rights Reserved             */
/***********************************************/

/*
    [References]:
        Quilez, I. (2014). intersectors. https://iquilezles.org/articles/intersectors/
        javidx9. (2021). Super Fast Ray Casting in Tiled Worlds using DDA. https://www.youtube.com/watch?v=NbSee-XM7WA
*/

#include "/include/voxels/intersections.glsl"

void raytraceVoxelLod1(inout VoxelIntersection hit, ivec3 node, vec3 origin, vec3 direction, int stepCount) {
    ivec3 index   = clamp(ivec3(floor(origin)), node, node | 3);
    ivec3 dirSign = ivec3(sign(direction));
    vec3  dirStep = dirSign * 0.5 + 0.5;
    
    for (int i = 0; i < stepCount && (index & -4) == node && !hit.intersect; i++) {
        vec3  steps    = (dirStep - (origin - index)) / direction;
        float stepSize = minOf(steps);
        ivec3 stepAxis = ivec3(lessThanEqual(steps, vec3(stepSize)));

        hit.position += direction * stepSize;

        hit.voxel = readVoxel(index);

        int id = unpackId(hit.voxel.packedData);

        vec3 localOrigin = origin - index - vec3(0.5);

        if (id != FULL_BLOCKS) {
            intersectVoxelBlockModel(hit, localOrigin, direction, index, int(id));
        } else {
            intersectBox(hit, localOrigin, direction, index, int(id), cube);
        }

        hit.intersect = hit.dists.x >= 0.0;

        index += stepAxis * dirSign;
    }
    return;
}

void raytraceVoxelLod4(inout VoxelIntersection hit, ivec3 node, vec3 origin, vec3 direction, int stepCount) {
    const int lod = 4, lod_sqrt = 2;

    ivec3 index   = clamp(ivec3(floor(origin)), node, node | 15) & -lod;
    ivec3 dirSign = ivec3(sign(direction));
    vec3  dirStep = (dirSign * 0.5 + 0.5) * lod;

    dirSign <<= lod_sqrt;
    
    for (int i = 0; i < stepCount && (index & -16) == node && !hit.intersect; i++) {
        vec3  steps    = (dirStep - (origin - (index & -lod))) / direction;
        float stepSize = minOf(steps);
        ivec3 stepAxis = ivec3(lessThanEqual(steps, vec3(stepSize)));

        hit.position += direction * stepSize;

        if (isInLod4(index)) {
            raytraceVoxelLod1(hit, index, origin, direction, 10);
            if (hit.intersect) return;
        }

        index += stepAxis * dirSign;
    }
    return;
}

VoxelIntersection raytraceVoxelLod16(vec3 origin, vec3 direction, int stepCount) {
    VoxelIntersection hit;
    hit.position  = origin;
    hit.intersect = false;
    hit.dists     = vec2(-1.0);

    const int lod = 16, lod_sqrt = 4;

    ivec3 index   = ivec3(floor(origin)) & -lod;
    ivec3 dirSign = ivec3(sign(direction));
    vec3  dirStep = (dirSign * 0.5 + 0.5) * lod;

    dirSign <<= lod_sqrt;
    
    for (int i = 0; i < stepCount && !hit.intersect && isInVoxelBounds(index); i++) {
        vec3  steps    = (dirStep - (origin - (index & -lod))) / direction;
        float stepSize = minOf(steps);
        ivec3 stepAxis = ivec3(lessThanEqual(steps, vec3(stepSize)));

        hit.position += direction * stepSize;

        if (isInLod16(index)) {
            raytraceVoxelLod4(hit, index, origin, direction, 10);
            if (hit.intersect) return hit;
        }

        index += stepAxis * dirSign;
    }
    return hit;
}

VoxelIntersection raytraceVoxel(vec3 origin, vec3 direction, int stepCount) {
    return raytraceVoxelLod16(origin, direction, stepCount);
}
