diff --git a/apps/typegpu-docs/src/examples/rendering/vector-field/index.html b/apps/typegpu-docs/src/examples/rendering/vector-field/index.html new file mode 100644 index 0000000000..aa8cc321b3 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/vector-field/index.html @@ -0,0 +1 @@ + diff --git a/apps/typegpu-docs/src/examples/rendering/vector-field/index.ts b/apps/typegpu-docs/src/examples/rendering/vector-field/index.ts new file mode 100644 index 0000000000..5975fa41cd --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/vector-field/index.ts @@ -0,0 +1,150 @@ +import { + caps, + endCapSlot, + LineControlPoint, + lineSegmentIndices, + lineSegmentVariableWidth, + startCapSlot, +} from '@typegpu/geometry'; +import tgpu, { d, std } from 'typegpu'; +import { defineControls } from '../../common/defineControls.ts'; + +const root = await tgpu.init(); + +const context = root.configureContext({ + canvas: document.querySelector('canvas')!, + alphaMode: 'premultiplied', +}); + +const GRID_SIZE = 4; + +const MAX_JOIN_COUNT = 1; +const indices = lineSegmentIndices(MAX_JOIN_COUNT); +const indexBuffer = root.createBuffer(d.arrayOf(d.u16, indices.length), indices).$usage('index'); + +const mainVertex = tgpu.vertexFn({ + in: { + instanceIndex: d.builtin.instanceIndex, + vertexIndex: d.builtin.vertexIndex, + }, + out: { + outPos: d.builtin.position, + }, +})(({ vertexIndex, instanceIndex: arrowIdx }) => { + 'use gpu'; + const arrowX = arrowIdx % GRID_SIZE; + const arrowY = d.u32(arrowIdx / GRID_SIZE); + // An arrow pointing to the top-right corner + const startPos = d.vec2f(arrowX, arrowY) / GRID_SIZE; + const endPos = (d.vec2f(arrowX, arrowY) + 1) / GRID_SIZE; + + const A = LineControlPoint({ + position: startPos, + radius: 0.02, + }); + const B = LineControlPoint({ + position: std.mix(startPos, endPos, 0.25), + radius: 0.02, + }); + const C = LineControlPoint({ + position: std.mix(startPos, endPos, 0.75), + radius: 0.02, + }); + const D = LineControlPoint({ + position: endPos, + radius: 0.02, + }); + + const result = lineSegmentVariableWidth(vertexIndex, A, B, D, D, MAX_JOIN_COUNT); + + return { + outPos: d.vec4f(result.vertexPosition, 0, 1), + }; +}); + +const mainFragment = tgpu.fragmentFn({ + out: d.vec4f, +})(() => { + 'use gpu'; + return d.vec4f(1, 0, 0, 1); +}); + +const pipeline = root + .with(startCapSlot, caps.butt) + .with(endCapSlot, caps.arrow) + .createRenderPipeline({ + vertex: mainVertex, + fragment: mainFragment, + }) + .withIndexBuffer(indexBuffer) + .withColorAttachment({ + view: context, + clearValue: [1, 1, 1, 1], + }); + +const draw = () => { + pipeline.drawIndexed(indices.length, GRID_SIZE * GRID_SIZE); +}; + +function frame() { + draw(); + frameId = requestAnimationFrame(frame); +} + +let frameId = requestAnimationFrame(frame); + +// #region Example controls & Cleanup + +export const controls = defineControls({ + Randomize: { + onButtonClick() {}, + }, +}); + +export function onCleanup() { + root.destroy(); + cancelAnimationFrame(frameId); +} + +// #endregion + +/* +import tgpu, { d, std } from 'typegpu'; +import { randf } from '@typegpu/noise'; +import type { AnyWgslData } from 'typegpu/data'; + +// { device: { optionalFeatures: ['shader-f16'] } } +const root = await tgpu.init(); + +const size = 4; + +const arrayNxN = (element: T, w: number, h: number) => + d.arrayOf(d.arrayOf(element, w), h); + +const displacementBuffer = root.createBuffer(arrayNxN(d.vec2h, size, size)).$usage('storage'); +const displacementPackedBuffer = root + .createBuffer(arrayNxN(d.u32, size, size), root.unwrap(displacementBuffer)) + .$usage('storage'); + +const displacementView = displacementBuffer.as('mutable'); +const displacementPackedView = displacementPackedBuffer.as('mutable'); + +function main(x: number, y: number) { + 'use gpu'; + + const dir = randf.onUnitCircle(); + + if (std.extensionEnabled('f16')) { + displacementView.$[x][y] = d.vec2h(dir); + } else { + displacementPackedView.$[x][y] = std.pack2x16float(dir); + } +} + +const pipeline = root.createGuardedComputePipeline(main); + +pipeline.dispatchThreads(size, size); + +console.log(await displacementBuffer.read()); + +*/ diff --git a/apps/typegpu-docs/src/examples/rendering/vector-field/meta.json b/apps/typegpu-docs/src/examples/rendering/vector-field/meta.json new file mode 100644 index 0000000000..502367de7d --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/vector-field/meta.json @@ -0,0 +1,7 @@ +{ + "title": "Vector Field", + "category": "simulation", + "tags": ["ecosystem", "line-rendering", "particles", "vector field"], + "dev": true, + "coolFactor": 7 +} diff --git a/apps/typegpu-docs/src/examples/rendering/vector-field/simple-line.ts b/apps/typegpu-docs/src/examples/rendering/vector-field/simple-line.ts new file mode 100644 index 0000000000..4884d855a6 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/vector-field/simple-line.ts @@ -0,0 +1,54 @@ +import { LineControlPoint, lineSegmentIndices, lineSegmentVariableWidth } from '@typegpu/geometry'; +import tgpu, { d, type TgpuRoot } from 'typegpu'; + +export function createLine(root: TgpuRoot, () => { }) { + const MAX_JOIN_COUNT = 1; + const indices = lineSegmentIndices(MAX_JOIN_COUNT); + + const maxIndex = Math.max(...indices); + const indexBuffer = root.createBuffer(d.arrayOf(d.u16, indices.length), indices).$usage('index'); + + const vertex = tgpu.vertexFn({ + in: { + instanceIndex: d.builtin.instanceIndex, + vertexIndex: d.builtin.vertexIndex, + }, + out: { + outPos: d.builtin.position, + }, + })(({ vertexIndex, instanceIndex: arrowIdx }) => { + 'use gpu'; + const arrowX = arrowIdx % GRID_SIZE; + const arrowY = d.u32(arrowIdx / GRID_SIZE); + // An arrow pointing to the top-right corner + const startPos = d.vec2f(arrowX, arrowY) / GRID_SIZE; + const endPos = (d.vec2f(arrowX, arrowY) + 1) / GRID_SIZE; + + const A = LineControlPoint({ + position: startPos, + radius: 0.02, + }); + const B = LineControlPoint({ + position: std.mix(startPos, endPos, 0.25), + radius: 0.02, + }); + const C = LineControlPoint({ + position: std.mix(startPos, endPos, 0.75), + radius: 0.02, + }); + const D = LineControlPoint({ + position: endPos, + radius: 0.02, + }); + + const result = lineSegmentVariableWidth(vertexIndex, A, B, D, D, MAX_JOIN_COUNT); + + return { + outPos: d.vec4f(result.vertexPosition, 0, 1), + }; + }); + + return { + vertex, + }; +} diff --git a/apps/typegpu-docs/src/examples/rendering/vector-field/thumbnail.png b/apps/typegpu-docs/src/examples/rendering/vector-field/thumbnail.png new file mode 100644 index 0000000000..0bc847b508 Binary files /dev/null and b/apps/typegpu-docs/src/examples/rendering/vector-field/thumbnail.png differ