diff --git a/apps/typegpu-docs/src/examples/rendering/marching-cubes/index.html b/apps/typegpu-docs/src/examples/rendering/marching-cubes/index.html new file mode 100644 index 000000000..aa8cc321b --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/marching-cubes/index.html @@ -0,0 +1 @@ + diff --git a/apps/typegpu-docs/src/examples/rendering/marching-cubes/index.ts b/apps/typegpu-docs/src/examples/rendering/marching-cubes/index.ts new file mode 100644 index 000000000..7aca43f97 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/marching-cubes/index.ts @@ -0,0 +1,507 @@ +import tgpu, { prepareDispatch } from 'typegpu'; +import * as d from 'typegpu/data'; +import * as std from 'typegpu/std'; +import * as m from 'wgpu-matrix'; +import * as p from './params.ts'; +import { Camera, modelVertexLayout, renderBindGroupLayout } from './schemas.ts'; +import { fragmentShader, vertexShader } from './render.ts'; +import { loadModel } from './load-model.ts'; +import { perlin3d, randf } from '@typegpu/noise'; +import { edgeTable, edgeToVertices, triangleTable } from './tables'; +import { SIZE } from './params.ts'; + +const root = await tgpu.init(); +const canvas = document.querySelector('canvas') as HTMLCanvasElement; +const context = canvas.getContext('webgpu') as GPUCanvasContext; +const presentationFormat = 'rgba16float'; + +context.configure({ + device: root.device, + format: presentationFormat, + alphaMode: 'premultiplied', +}); + +const terrainTexture = root['~unstable'].createTexture({ + size: [SIZE, SIZE, SIZE], + format: presentationFormat, + dimension: '3d', +}).$usage('sampled', 'render', 'storage'); + +// fill texture with noise + +const fillBindGroupLayout = tgpu.bindGroupLayout({ + terrain: { storageTexture: d.textureStorage3d('rgba16float', 'write-only') }, +}); + +const fillBindGroup = root.createBindGroup(fillBindGroupLayout, { + terrain: terrainTexture, +}); + +prepareDispatch(root, (x, y, z) => { + 'kernel'; + // const level = + // std.distance(d.vec3f(x, y, z), d.vec3f(SIZE / 2, SIZE / 2, SIZE / 2)) / + // SIZE / 0.5; + // randf.seed(x * SIZE * SIZE + y * SIZE + z); + // const level = randf.sample(); + let level = d.f32(y) / SIZE - 0.3; + for (let i = 0; i < 4; i++) { + const mult = 3 * d.f32(2 ** i); + level += perlin3d.sample(d.vec3f(x, y, z).div(SIZE).mul(mult)) / mult; + } + std.textureStore( + fillBindGroupLayout.$.terrain, + d.vec3u(x, y, z), + d.vec4f(level, 0, 0, 0), + ); +}) + .with(fillBindGroupLayout, fillBindGroup) + .dispatch(SIZE, SIZE, SIZE); + +// --- generate triangles --- + +const Point = d.vec3f; +const Triangle = d.struct({ points: d.arrayOf(Point, 3), normal: d.vec3f }); +const CellTriangles = d.struct({ + count: d.u32, + triangles: d.arrayOf(Triangle, 4), +}); + +const indexMutable = root.createMutable(d.atomic(d.u32), 0); +const trianglesMutable = root.createMutable( + d.arrayOf(Triangle, 4 * ((SIZE - 1) ** 3)), +); + +const generateBindGroupLayout = tgpu.bindGroupLayout({ + terrain: { storageTexture: d.textureStorage3d('rgba16float', 'read-only') }, +}); + +const generateBindGroup = root.createBindGroup(generateBindGroupLayout, { + terrain: terrainTexture, +}); + +const GridCell = d.struct({ + vertex: d.arrayOf(Point, 8), + value: d.arrayOf(d.f32, 8), +}); + +/** + * Given a `cell`, calculate its cube index + * The cube index is an 8-bit encoding. Each bit represents a vertex. `index[i]` is the ith bit + * If the value at the ith vertex is < isovalue, `index[i]` = 1. Else, `index[i]` = 0 + */ +const calculateCubeIndex = tgpu.fn([GridCell, d.f32], d.u32)( + (cell, isoValue) => { + 'kernel'; + let cubeIndex = d.u32(0); + for (let i = d.u32(0); i < 8; i++) { + if (cell.value[i] < isoValue) { + cubeIndex |= d.u32(d.i32(1 << i)); + } + } + return cubeIndex; + }, +); + +// Find the point between `v1` and `v2` where the functional value = `isovalue` +const interpolate = tgpu.fn([Point, d.f32, Point, d.f32, d.f32], Point)( + (v1, val1, v2, val2, isoValue) => { + 'kernel'; + const interpolated = Point(); + const mu = (isoValue - val1) / (val2 - val1); + + interpolated.x = mu * (v2.x - v1.x) + v1.x; + interpolated.y = mu * (v2.y - v1.y) + v1.y; + interpolated.z = mu * (v2.z - v1.z) + v1.z; + + return interpolated; + }, +); + +// Returns all intersection coordinates of a cell with the isosurface +// (Calls `interpolate()`) +const getIntersectionCoordinates = tgpu.fn( + [GridCell, d.f32], + d.arrayOf(Point, 12), +)((cell, isoValue) => { + 'kernel'; + const intersections = d.arrayOf(Point, 12)(); + const cubeIndex = calculateCubeIndex(cell, isoValue); + + let intersectionsKey = edgeTable.$[cubeIndex]; + let idx = 0; + while (intersectionsKey) { + if (intersectionsKey & 1) { + const v1 = edgeToVertices.$[idx][0]; + const v2 = edgeToVertices.$[idx][1]; + const intersectionPoint = interpolate( + cell.vertex[v1], + cell.value[v1], + cell.vertex[v2], + cell.value[v2], + isoValue, + ); + intersections[idx] = intersectionPoint; + } + idx++; + intersectionsKey >>= 1; + } + + return intersections; +}); + +const calculateNormal = tgpu.fn([d.arrayOf(Point, 3)], d.vec3f)((points) => { + const e1 = points[1].sub(points[0]); + const e2 = points[2].sub(points[0]); + const n = std.cross(d.vec3f(e1.x, e1.y, e1.z), d.vec3f(e2.x, e2.y, e2.z)); + return std.normalize(n); +}); + +// Given `cubeIndex`, get the edge table entry and using `intersections`, make all triangles +const getTriangles = tgpu.fn( + [d.arrayOf(Point, 12), d.u32], + CellTriangles, +)((intersections, cubeIndex) => { + 'kernel'; + const triangles = d.arrayOf(Triangle, 4)(); + let count = 0; + for (let i = 0; triangleTable.$[cubeIndex][i] != -1; i += 3) { + const triangle = Triangle(); + for (let j = 0; j < 3; j++) { + triangle.points[j] = intersections[triangleTable.$[cubeIndex][i + j]]; + } + triangle.normal = calculateNormal(triangle.points); + triangles[count] = triangle; + count += 1; + } + + return { count, triangles }; +}); + +// Get triangles of a single cell +const triangulateCell = tgpu.fn([GridCell, d.f32], CellTriangles)( + (cell, isoValue) => { + 'kernel'; + const cubeIndex = calculateCubeIndex(cell, isoValue); + const intersections = getIntersectionCoordinates(cell, isoValue); + const triangles = getTriangles(intersections, cubeIndex); + + return triangles; + }, +); + +// Triangulate a scalar field represented by `scalarFunction`. `isovalue` should be used for isovalue computation +const triangulateField = prepareDispatch(root, (x, y, z) => { + 'kernel'; + const cell = GridCell( + { + vertex: [ + d.vec3f(x, y, z), + d.vec3f(x + 1, y, z), + d.vec3f(x + 1, y, z + 1), + d.vec3f(x, y, z + 1), + d.vec3f(x, y + 1, z), + d.vec3f(x + 1, y + 1, z), + d.vec3f(x + 1, y + 1, z + 1), + d.vec3f(x, y + 1, z + 1), + ], + value: [ + loadValue(x, y, z), + loadValue(x + 1, y, z), + loadValue(x + 1, y, z + 1), + loadValue(x, y, z + 1), + loadValue(x, y + 1, z), + loadValue(x + 1, y + 1, z), + loadValue(x + 1, y + 1, z + 1), + loadValue(x, y + 1, z + 1), + ], + }, + ); + const triangles = triangulateCell(cell, 0); + + for (let i = 0; i < triangles.count; i++) { + const triangleIndex = std.atomicAdd(indexMutable.$, 1); + trianglesMutable.$[triangleIndex] = triangles.triangles[i]; + } +}); + +const loadValue = tgpu.fn([d.u32, d.u32, d.u32], d.f32)((x, y, z) => { + 'kernel'; + const textureValue = std.textureLoad( + generateBindGroupLayout.$.terrain, + d.vec3u(x, y, z), + ); + return textureValue.x; +}); + +triangulateField + .with(generateBindGroupLayout, generateBindGroup) + .dispatch(SIZE - 1, SIZE - 1, SIZE - 1); + +// --- RENDER --- + +context.configure({ + device: root.device, + format: presentationFormat, + alphaMode: 'premultiplied', +}); + +// model + +const triangleCount = await indexMutable.read(); +const triangles = await trianglesMutable.read(); +const vertexedTriangles = triangles.map(( + triangle, +) => (triangle.points.map((vertex) => ({ + modelPosition: d.vec3f(vertex.x, vertex.y, vertex.z), + modelNormal: triangle.normal, +})))).flat(); +const vertexBuffer = root.createBuffer( + d.arrayOf( + d.struct({ modelPosition: d.vec3f, modelNormal: d.vec3f }), + vertexedTriangles.length, + ), + vertexedTriangles, +).$usage('vertex'); + +const fishModel = { + vertexBuffer, + polygonCount: triangleCount, +}; + +// // https://sketchfab.com/3d-models/animated-low-poly-fish-64adc2e5a4be471e8279532b9610c878 +// const fishModel = await loadModel(root, '/TypeGPU/assets/3d-fish/fish.obj'); + +// buffers + +const camera = { + position: p.cameraInitialPosition, + targetPos: p.cameraInitialTarget, + view: m.mat4.lookAt( + p.cameraInitialPosition, + p.cameraInitialTarget, + d.vec3f(0, 1, 0), + d.mat4x4f(), + ), + projection: m.mat4.perspective( + Math.PI / 4, + canvas.clientWidth / canvas.clientHeight, + 0.1, + 1000, + d.mat4x4f(), + ), +}; + +const cameraBuffer = root.createBuffer(Camera, camera).$usage('uniform'); + +// pipelines + +const renderPipeline = root['~unstable'] + .withVertex(vertexShader, modelVertexLayout.attrib) + .withFragment(fragmentShader, { format: presentationFormat }) + .withDepthStencil({ + format: 'depth24plus', + depthWriteEnabled: true, + depthCompare: 'less', + }) + .withPrimitive({ topology: 'triangle-list' }) + .withPrimitive({ cullMode: 'back' }) + .createPipeline(); + +let depthTexture = root.device.createTexture({ + size: [canvas.width, canvas.height, 1], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, +}); + +// bind groups + +const renderFishBindGroup = root.createBindGroup(renderBindGroupLayout, { + camera: cameraBuffer, +}); + +// frame + +let disposed = false; + +function frame() { + if (disposed) { + return; + } + + renderPipeline + .withColorAttachment({ + view: context.getCurrentTexture().createView(), + clearValue: [ + p.backgroundColor.x, + p.backgroundColor.y, + p.backgroundColor.z, + 1, + ], + loadOp: 'clear', + storeOp: 'store', + }) + .withDepthStencilAttachment({ + view: depthTexture.createView(), + depthClearValue: 1, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }) + .with(modelVertexLayout, fishModel.vertexBuffer) + .with(renderBindGroupLayout, renderFishBindGroup) + .draw(fishModel.polygonCount * 3); + + root['~unstable'].flush(); + + requestAnimationFrame(frame); +} +requestAnimationFrame(frame); + +// ---- + +// #region Example controls and cleanup + +const resizeObserver = new ResizeObserver(() => { + camera.projection = m.mat4.perspective( + Math.PI / 4, + canvas.clientWidth / canvas.clientHeight, + 0.1, + 1000, + d.mat4x4f(), + ); + + depthTexture.destroy(); + depthTexture = root.device.createTexture({ + size: [canvas.width, canvas.height, 1], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); +}); +resizeObserver.observe(canvas); + +// Variables for mouse interaction. +let isDragging = false; +let prevX = 0; +let prevY = 0; +let orbitRadius = std.length(p.cameraInitialPosition); + +// Yaw and pitch angles facing the origin. +let orbitYaw = Math.atan2(p.cameraInitialPosition.x, p.cameraInitialPosition.z); +let orbitPitch = Math.asin(p.cameraInitialPosition.y / orbitRadius); + +function updateCameraOrbit(dx: number, dy: number) { + const orbitSensitivity = 0.005; + orbitYaw += -dx * orbitSensitivity; + orbitPitch += dy * orbitSensitivity; + // Clamp pitch to avoid flipping + const maxPitch = Math.PI / 2 - 0.01; + if (orbitPitch > maxPitch) orbitPitch = maxPitch; + if (orbitPitch < -maxPitch) orbitPitch = -maxPitch; + // Convert spherical coordinates to cartesian coordinates + const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch); + const newCamY = orbitRadius * Math.sin(orbitPitch); + const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch); + const newCameraPos = d.vec4f(newCamX, newCamY, newCamZ, 1); + + const newView = m.mat4.lookAt( + newCameraPos, + d.vec3f(0, 0, 0), + d.vec3f(0, 1, 0), + d.mat4x4f(), + ); + cameraBuffer.writePartial({ view: newView, position: newCameraPos }); +} + +canvas.addEventListener('wheel', (event: WheelEvent) => { + event.preventDefault(); + const zoomSensitivity = 0.05; + orbitRadius = std.clamp( + orbitRadius + event.deltaY * zoomSensitivity, + 3, + 1000, + ); + const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch); + const newCamY = orbitRadius * Math.sin(orbitPitch); + const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch); + const newCameraPos = d.vec4f(newCamX, newCamY, newCamZ, 1); + const newView = m.mat4.lookAt( + newCameraPos, + d.vec3f(0, 0, 0), + d.vec3f(0, 1, 0), + d.mat4x4f(), + ); + cameraBuffer.writePartial({ view: newView, position: newCameraPos }); +}, { passive: false }); + +canvas.addEventListener('mousedown', (event) => { + isDragging = true; + prevX = event.clientX; + prevY = event.clientY; +}); + +canvas.addEventListener('touchstart', (event) => { + event.preventDefault(); + if (event.touches.length === 1) { + isDragging = true; + prevX = event.touches[0].clientX; + prevY = event.touches[0].clientY; + } +}, { passive: false }); + +const mouseUpEventListener = () => { + isDragging = false; +}; +window.addEventListener('mouseup', mouseUpEventListener); + +const touchEndEventListener = () => { + isDragging = false; +}; +window.addEventListener('touchend', touchEndEventListener); + +const mouseMoveEventListener = (event: MouseEvent) => { + const dx = event.clientX - prevX; + const dy = event.clientY - prevY; + prevX = event.clientX; + prevY = event.clientY; + + if (isDragging) { + updateCameraOrbit(dx, dy); + } +}; +window.addEventListener('mousemove', mouseMoveEventListener); + +const touchMoveEventListener = (event: TouchEvent) => { + if (isDragging && event.touches.length === 1) { + event.preventDefault(); + const dx = event.touches[0].clientX - prevX; + const dy = event.touches[0].clientY - prevY; + prevX = event.touches[0].clientX; + prevY = event.touches[0].clientY; + + updateCameraOrbit(dx, dy); + } +}; +window.addEventListener('touchmove', touchMoveEventListener, { + passive: false, +}); + +function hideHelp() { + const helpElem = document.getElementById('help'); + if (helpElem) { + helpElem.style.opacity = '0'; + } +} +for (const eventName of ['click', 'keydown', 'wheel', 'touchstart']) { + canvas.addEventListener(eventName, hideHelp, { once: true, passive: true }); +} + +export function onCleanup() { + window.removeEventListener('mouseup', mouseUpEventListener); + window.removeEventListener('mousemove', mouseMoveEventListener); + window.removeEventListener('touchmove', touchMoveEventListener); + window.removeEventListener('touchend', touchEndEventListener); + resizeObserver.unobserve(canvas); + root.destroy(); +} + +// #endregion diff --git a/apps/typegpu-docs/src/examples/rendering/marching-cubes/load-model.ts b/apps/typegpu-docs/src/examples/rendering/marching-cubes/load-model.ts new file mode 100644 index 000000000..df7ed3321 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/marching-cubes/load-model.ts @@ -0,0 +1,41 @@ +import { load } from '@loaders.gl/core'; +import { OBJLoader } from '@loaders.gl/obj'; +import type { TgpuRoot } from 'typegpu'; +import * as d from 'typegpu/data'; +import { modelVertexLayout } from './schemas.ts'; + +export async function loadModel( + root: TgpuRoot, + modelPath: string, +) { + const modelMesh = await load(modelPath, OBJLoader); + const polygonCount = modelMesh.attributes.POSITION.value.length / 3; + + const vertexBuffer = root + .createBuffer(modelVertexLayout.schemaForCount(polygonCount)) + .$usage('vertex') + .$name(`model vertices of ${modelPath}`); + + const modelVertices = []; + for (let i = 0; i < polygonCount; i++) { + modelVertices.push({ + modelPosition: d.vec3f( + modelMesh.attributes.POSITION.value[3 * i], + modelMesh.attributes.POSITION.value[3 * i + 1], + modelMesh.attributes.POSITION.value[3 * i + 2], + ), + modelNormal: d.vec3f( + modelMesh.attributes.NORMAL.value[3 * i], + modelMesh.attributes.NORMAL.value[3 * i + 1], + modelMesh.attributes.NORMAL.value[3 * i + 2], + ), + }); + } + + vertexBuffer.write(modelVertices); + + return { + vertexBuffer, + polygonCount, + }; +} diff --git a/apps/typegpu-docs/src/examples/rendering/marching-cubes/meta.json b/apps/typegpu-docs/src/examples/rendering/marching-cubes/meta.json new file mode 100644 index 000000000..a43a5d78d --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/marching-cubes/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Marching Cubes", + "category": "rendering", + "tags": ["experimental"] +} diff --git a/apps/typegpu-docs/src/examples/rendering/marching-cubes/params.ts b/apps/typegpu-docs/src/examples/rendering/marching-cubes/params.ts new file mode 100644 index 000000000..d44b55346 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/marching-cubes/params.ts @@ -0,0 +1,11 @@ +import * as d from 'typegpu/data'; +import * as std from 'typegpu/std'; + +export const SIZE = 64; + +export const cameraInitialPosition = d.vec4f(-10, 0, -10, 1); +export const cameraInitialTarget = d.vec4f(0, 0, 0, 1); // does not work... + +export const lightColor = d.vec3f(0.9, 0.9, 0.8); +export const lightDirection = std.normalize(d.vec3f(-1.0, 4.0, -1.0)); +export const backgroundColor = d.vec3f(0x00, 0x7a, 0xcc).div(255); diff --git a/apps/typegpu-docs/src/examples/rendering/marching-cubes/render.ts b/apps/typegpu-docs/src/examples/rendering/marching-cubes/render.ts new file mode 100644 index 000000000..9d3709311 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/marching-cubes/render.ts @@ -0,0 +1,63 @@ +import { hsvToRgb, rgbToHsv } from '@typegpu/color'; +import tgpu from 'typegpu'; +import * as d from 'typegpu/data'; +import * as p from './params.ts'; +import * as std from 'typegpu/std'; +import { + ModelVertexInput, + ModelVertexOutput, + renderBindGroupLayout as layout, +} from './schemas.ts'; + +export const vertexShader = tgpu['~unstable'].vertexFn({ + in: { ...ModelVertexInput.propTypes, instanceIndex: d.builtin.instanceIndex }, + out: ModelVertexOutput, +})((input) => { + const worldPosition = d.vec4f(input.modelPosition, 1); + const camera = layout.$.camera; + + const canvasPosition = std.mul( + camera.projection, + std.mul(camera.view, worldPosition), + ); + + return { + worldPosition: input.modelPosition, + worldNormal: input.modelNormal, + canvasPosition: canvasPosition, + }; +}); + +export const fragmentShader = tgpu['~unstable'].fragmentFn({ + in: ModelVertexOutput, + out: d.vec4f, +})((input) => { + // shade the fragment in Phong reflection model + // https://en.wikipedia.org/wiki/Phong_reflection_model + // then apply sea fog and sea desaturation + const textureColor = d.vec3f(0.8, 0.8, 0.1); + + const ambient = std.mul(0.5, std.mul(textureColor, p.lightColor)); + + const cosTheta = std.dot(input.worldNormal, p.lightDirection); + const diffuse = std.mul( + std.max(0, cosTheta), + std.mul(textureColor, p.lightColor), + ); + + const viewSource = std.normalize( + std.sub(layout.$.camera.position.xyz, input.worldPosition), + ); + const reflectSource = std.normalize( + std.reflect(std.mul(-1, p.lightDirection), input.worldNormal), + ); + const specularStrength = std.pow( + std.max(0, std.dot(viewSource, reflectSource)), + 5, + ); + const specular = std.mul(specularStrength * 0.2, p.lightColor); + + const lightedColor = std.add(ambient, std.add(diffuse, specular)); + + return d.vec4f(lightedColor, 1); +}); diff --git a/apps/typegpu-docs/src/examples/rendering/marching-cubes/schemas.ts b/apps/typegpu-docs/src/examples/rendering/marching-cubes/schemas.ts new file mode 100644 index 000000000..daaef5078 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/marching-cubes/schemas.ts @@ -0,0 +1,32 @@ +import tgpu from 'typegpu'; +import * as d from 'typegpu/data'; + +// schemas + +export const Camera = d.struct({ + position: d.vec4f, + targetPos: d.vec4f, + view: d.mat4x4f, + projection: d.mat4x4f, +}); + +export const ModelVertexInput = d.struct({ + modelPosition: d.vec3f, + modelNormal: d.vec3f, +}); + +export const ModelVertexOutput = { + worldPosition: d.vec3f, + worldNormal: d.vec3f, + canvasPosition: d.builtin.position, +} as const; + +// layouts + +export const modelVertexLayout = tgpu.vertexLayout((n: number) => + d.arrayOf(ModelVertexInput, n) +); + +export const renderBindGroupLayout = tgpu.bindGroupLayout({ + camera: { uniform: Camera }, +}); diff --git a/apps/typegpu-docs/src/examples/rendering/marching-cubes/tables.ts b/apps/typegpu-docs/src/examples/rendering/marching-cubes/tables.ts new file mode 100644 index 000000000..25ee89eba --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/marching-cubes/tables.ts @@ -0,0 +1,323 @@ +import tgpu from 'typegpu'; +import * as d from 'typegpu/data'; + +export const vertexOffsets = tgpu.const(d.arrayOf(d.vec3i, 8), [ + d.vec3i(0, 0, 0), + d.vec3i(1, 0, 0), + d.vec3i(1, 1, 0), + d.vec3i(0, 1, 0), + d.vec3i(0, 0, 1), + d.vec3i(1, 0, 1), + d.vec3i(1, 1, 1), + d.vec3i(0, 1, 1), +]); + +export const edgeToVertices = tgpu.const(d.arrayOf(d.vec2i, 12), [ + d.vec2i(0, 1), + d.vec2i(1, 2), + d.vec2i(2, 3), + d.vec2i(0, 3), + d.vec2i(4, 5), + d.vec2i(5, 6), + d.vec2i(6, 7), + d.vec2i(4, 7), + d.vec2i(0, 4), + d.vec2i(1, 5), + d.vec2i(2, 6), + d.vec2i(3, 7), +]); + +// deno-fmt-ignore +export const edgeTable = tgpu.const(d.arrayOf(d.u32, 256), [ + 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 +]); + +export const triangleTable = tgpu.const(d.arrayOf(d.arrayOf(d.i32, 16), 256), [ + [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1], + [3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1], + [3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1], + [3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1], + [9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1], + [9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1], + [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1], + [8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1], + [9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1], + [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1], + [3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1], + [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1], + [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1], + [4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1], + [9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1], + [5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1], + [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1], + [9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1], + [0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1], + [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1], + [10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1], + [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1], + [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1], + [5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1], + [9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1], + [0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1], + [1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1], + [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1], + [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1], + [2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1], + [7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1], + [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1], + [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1], + [11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1], + [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1], + [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1], + [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1], + [11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1], + [1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1], + [9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1], + [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1], + [2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1], + [0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1], + [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1], + [6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1], + [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1], + [6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1], + [5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1], + [1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1], + [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1], + [6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1], + [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1], + [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1], + [3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1], + [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1], + [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1], + [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1], + [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1], + [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1], + [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1], + [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1], + [10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1], + [10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1], + [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1], + [1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1], + [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1], + [0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1], + [10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1], + [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1], + [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1], + [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1], + [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1], + [3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1], + [6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1], + [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1], + [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1], + [10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1], + [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1], + [7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1], + [7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1], + [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1], + [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1], + [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1], + [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1], + [0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1], + [7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1], + [10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1], + [2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1], + [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1], + [7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1], + [2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1], + [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1], + [10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1], + [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1], + [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1], + [7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1], + [6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1], + [8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1], + [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1], + [6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1], + [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1], + [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1], + [8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1], + [0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1], + [1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1], + [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1], + [10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1], + [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1], + [10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1], + [5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1], + [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1], + [9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1], + [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1], + [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1], + [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1], + [7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1], + [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1], + [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1], + [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1], + [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1], + [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1], + [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1], + [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1], + [6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1], + [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1], + [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1], + [6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1], + [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1], + [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1], + [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1], + [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1], + [9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1], + [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1], + [1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1], + [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1], + [0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1], + [5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1], + [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1], + [11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1], + [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1], + [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1], + [2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1], + [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1], + [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1], + [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1], + [1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1], + [9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1], + [9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1], + [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1], + [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1], + [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1], + [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1], + [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1], + [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1], + [9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1], + [5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1], + [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1], + [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1], + [8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1], + [0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1], + [9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1], + [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1], + [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1], + [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1], + [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1], + [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1], + [11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1], + [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1], + [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1], + [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1], + [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1], + [1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1], + [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1], + [4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1], + [0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1], + [3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1], + [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1], + [0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1], + [9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1], + [1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], +]);