// translucent quad sorting, the way sodium does it (its "translucency sorting"), simplified for quads // that all face along an axis. // every chunk's translucent mesh gets a sort type when it's meshed: // - none: the order can't matter, one quad or all of them in a single plane // - static: all quads face along one axis, so sorting them by distance along their normal once is right // from anywhere the camera can see them // - dynamic: sorted by distance to the camera, and sorted again only when the camera crosses one of the // planes the chunk's quads lie on, the only time the order between them can change (sodium's GFNI) export type SortType = "none" | "static" | "dynamic"; // same order as the mesher's faces export const FACE_NORMALS = [ [0, 1, 0], // top [0, -1, 0], // bottom [0, 0, 1], // front [0, 0, -1], // back [-1, 0, 0], // left [1, 0, 0], // right ] as const; export const FACE_AXIS = [1, 1, 2, 2, 0, 0] as const; // for each quad: its center, and which face it is export interface TranslucentQuads { centers: Float32Array; faces: Uint8Array; count: number; } export function choose_sort_type(quads: TranslucentQuads): SortType { if (quads.count <= 1) { return "none"; } let axes = 0; for (let q = 0; q < quads.count; q++) { axes |= 1 << FACE_AXIS[quads.faces[q]]; } // more than one axis, the order depends on where the camera is if (axes & (axes - 1)) { return "dynamic"; } // quads facing opposite ways on the same axis are never both visible over each other, so only // the distance along the axis matters. if they all share a plane, nothing can overlap at all const axis = FACE_AXIS[quads.faces[0]]; const plane = quads.centers[axis]; for (let q = 1; q < quads.count; q++) { if (quads.centers[q * 3 + axis] !== plane) { return "static"; } } return "none"; } // the unique coordinates of the planes the quads lie on, per axis, sorted. the camera crossing one of // these is what triggers a dynamic sort export function quad_planes(quads: TranslucentQuads): [Float32Array, Float32Array, Float32Array] { const sets = [new Set(), new Set(), new Set()]; for (let q = 0; q < quads.count; q++) { const axis = FACE_AXIS[quads.faces[q]]; sets[axis].add(quads.centers[q * 3 + axis]); } return sets.map((set) => Float32Array.from(set).sort()) as [Float32Array, Float32Array, Float32Array]; } // whether moving the camera from a to b crosses any of the planes export function crosses_planes(planes: [Float32Array, Float32Array, Float32Array], a: number[], b: number[]) { for (let axis = 0; axis < 3; axis++) { if (a[axis] === b[axis] || planes[axis].length === 0) { continue; } if (count_below(planes[axis], a[axis]) !== count_below(planes[axis], b[axis])) { return true; } } return false; } function count_below(sorted: Float32Array, value: number) { let lo = 0; let hi = sorted.length; while (lo < hi) { const mid = (lo + hi) >> 1; if (sorted[mid] < value) { lo = mid + 1; } else { hi = mid; } } return lo; } // back to front, for the camera at camera_x/y/z when the sort type is dynamic export function sort_quads( quads: TranslucentQuads, sort_type: SortType, camera_x: number, camera_y: number, camera_z: number, ): Uint32Array { const { centers, faces, count } = quads; if (sort_type === "dynamic") { return quad_indices(sort_by_distance(centers, count, camera_x, camera_y, camera_z)); } const order = new Uint32Array(count); for (let q = 0; q < count; q++) { order[q] = q; } if (sort_type === "static") { // for quads facing the camera, the ones further along their normal are closer to it const keys = new Float32Array(count); for (let q = 0; q < count; q++) { const [nx, ny, nz] = FACE_NORMALS[faces[q]]; keys[q] = centers[q * 3] * nx + centers[q * 3 + 1] * ny + centers[q * 3 + 2] * nz; } order.sort((a, b) => keys[a] - keys[b]); } return quad_indices(order); } // dynamic sorting only needs the centers, so resorting doesn't need the whole mesh export function sort_by_distance( centers: Float32Array, count: number, camera_x: number, camera_y: number, camera_z: number, ): Uint32Array { const order = new Uint32Array(count); const distances = new Float32Array(count); for (let q = 0; q < count; q++) { order[q] = q; const dx = centers[q * 3] - camera_x; const dy = centers[q * 3 + 1] - camera_y; const dz = centers[q * 3 + 2] - camera_z; distances[q] = dx * dx + dy * dy + dz * dz; } return order.sort((a, b) => distances[b] - distances[a]); } // two triangles per quad, drawn in the given order export function quad_indices(order: Uint32Array): Uint32Array { const indices = new Uint32Array(order.length * 6); for (let i = 0; i < order.length; i++) { const v = order[i] * 4; const o = i * 6; indices[o] = v; indices[o + 1] = v + 1; indices[o + 2] = v + 2; indices[o + 3] = v; indices[o + 4] = v + 2; indices[o + 5] = v + 3; } return indices; }