Skip to content

Commit 6847c28

Browse files
authored
Feat: homepage examples duplication (#2121)
1 parent 2c2b0e6 commit 6847c28

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+5228
-16
lines changed

apps/typegpu-docs/src/components/HoverExampleIsland.tsx

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ import type { Example } from '../utils/examples/types.ts';
1111

1212
type Props = {
1313
exampleKey: string;
14+
examplesConfig: Record<string, { html: string; tsPath: string }>;
1415
};
1516

16-
export default function HoverExampleIsland({ exampleKey }: Props) {
17+
export default function HoverExampleIsland(
18+
{ exampleKey, examplesConfig }: Props,
19+
) {
1720
const rootRef = useRef<HTMLDivElement | null>(null);
1821
const containerRef = useRef<HTMLDivElement | null>(null);
1922
const cleanupRef = useRef<(() => void) | undefined>(undefined);
@@ -119,7 +122,7 @@ export default function HoverExampleIsland({ exampleKey }: Props) {
119122
throw new Error('WebGPU is not enabled/supported in this browser.');
120123
}
121124

122-
const example = await loadExample(exampleKey);
125+
const example = await loadExample(exampleKey, examplesConfig);
123126
if (cancelled) {
124127
return;
125128
}
@@ -159,7 +162,7 @@ export default function HoverExampleIsland({ exampleKey }: Props) {
159162
cancelled = true;
160163
reset();
161164
};
162-
}, [exampleKey, isHovered, reset]);
165+
}, [exampleKey, isHovered, reset, examplesConfig]);
163166

164167
return (
165168
<div
@@ -192,16 +195,29 @@ export default function HoverExampleIsland({ exampleKey }: Props) {
192195
);
193196
}
194197

195-
async function loadExample(exampleKey: string): Promise<Example> {
196-
const exampleContent = await import('../examples/exampleContent.ts');
197-
const examples = exampleContent.examples as Record<string, Example>;
198-
199-
const example = examples[exampleKey] as Example | undefined;
200-
if (!example) {
198+
async function loadExample(
199+
exampleKey: string,
200+
examplesConfig: Record<string, { html: string; tsPath: string }>,
201+
): Promise<Example> {
202+
const config = examplesConfig[exampleKey];
203+
if (!config) {
201204
throw new Error(`Example "${exampleKey}" not found.`);
202205
}
203206

204-
return example;
207+
const tsImport = () => import(`${config.tsPath}?t=${Date.now()}`); // trick to prevent caching
208+
209+
return {
210+
key: exampleKey,
211+
metadata: { title: exampleKey, category: 'landing', tags: [] },
212+
tsFiles: [],
213+
tsImport,
214+
htmlFile: {
215+
exampleKey,
216+
path: 'index.html',
217+
content: config.html,
218+
},
219+
thumbnails: { small: '', large: '' },
220+
} satisfies Example;
205221
}
206222

207223
function resizeCanvases(container: HTMLElement) {

apps/typegpu-docs/src/components/TgpuExamples.astro

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,60 @@ import ReflectionGif from "../assets/hero/reflection_bg.jpg";
99
import vaporRaveGif from "../assets/hero/rave_bg.webp";
1010
1111
import HoverExampleIsland from "./HoverExampleIsland";
12+
import fishHtml from "../pages/landing-examples/3d-fish/index.html?raw";
13+
import causticsHtml from "../pages/landing-examples/caustics/index.html?raw";
14+
import reflectionHtml from "../pages/landing-examples/cubemap-reflection/index.html?raw";
15+
import jellyHtml from "../pages/landing-examples/jelly-slider/index.html?raw";
16+
import rayMarchingHtml from "../pages/landing-examples/ray-marching/index.html?raw";
17+
import vaporRaveHtml from "../pages/landing-examples/vaporrave/index.html?raw";
18+
19+
const examplesBasePath = "../pages/landing-examples";
1220
1321
const galleryItems = [
1422
{
1523
asset: ReflectionGif,
1624
title: "Reflection",
17-
exampleKey: "rendering--cubemap-reflection",
25+
exampleKey: "cubemap-reflection",
26+
html: reflectionHtml,
1827
},
1928
{
2029
asset: JellySliderGif,
2130
title: "Jelly Slider",
22-
exampleKey: "rendering--jelly-slider",
31+
exampleKey: "jelly-slider",
32+
html: jellyHtml,
2333
},
2434
{
2535
asset: vaporRaveGif,
2636
title: "VaporRave",
27-
exampleKey: "simple--vaporrave",
37+
exampleKey: "vaporrave",
38+
html: vaporRaveHtml,
2839
},
2940
{
3041
asset: RayMarchingGif,
3142
title: "Ray Marching",
32-
exampleKey: "rendering--ray-marching",
43+
exampleKey: "ray-marching",
44+
html: rayMarchingHtml,
3345
},
3446
{
3547
asset: FishGif,
3648
title: "3D Fish",
37-
exampleKey: "rendering--3d-fish",
49+
exampleKey: "3d-fish",
50+
html: fishHtml,
3851
},
3952
{
4053
asset: CausticsGif,
4154
title: "Caustics",
42-
exampleKey: "rendering--caustics",
55+
exampleKey: "caustics",
56+
html: causticsHtml,
4357
},
4458
];
59+
60+
const examplesConfig = Object.fromEntries(
61+
galleryItems.map(item => [
62+
item.exampleKey,
63+
{ html: item.html, tsPath: `${examplesBasePath}/${item.exampleKey}/index.ts` },
64+
])
65+
);
4566
---
4667

4768
{/* Interactive gallery */}
@@ -86,6 +107,7 @@ const galleryItems = [
86107
<HoverExampleIsland
87108
client:visible
88109
exampleKey={item.exampleKey}
110+
examplesConfig={examplesConfig}
89111
/>
90112
</div>
91113
</div>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { d, std } from 'typegpu';
2+
import * as p from './params.ts';
3+
import { computeBindGroupLayout as layout } from './schemas.ts';
4+
import { projectPointOnLine } from './tgsl-helpers.ts';
5+
6+
export const simulate = (fishIndex: number) => {
7+
'use gpu';
8+
const fishData = layout.$.currentFishData[fishIndex];
9+
let separation = d.vec3f();
10+
let alignment = d.vec3f();
11+
let alignmentCount = 0;
12+
let cohesion = d.vec3f();
13+
let cohesionCount = 0;
14+
let wallRepulsion = d.vec3f();
15+
let rayRepulsion = d.vec3f();
16+
17+
for (let i = 0; i < p.fishAmount; i += 1) {
18+
if (d.u32(i) === fishIndex) {
19+
continue;
20+
}
21+
22+
const other = layout.$.currentFishData[i];
23+
const dist = std.length(fishData.position.sub(other.position));
24+
if (dist < layout.$.fishBehavior.separationDist) {
25+
separation = separation.add(fishData.position.sub(other.position));
26+
}
27+
if (dist < layout.$.fishBehavior.alignmentDist) {
28+
alignment = alignment.add(other.direction);
29+
alignmentCount = alignmentCount + 1;
30+
}
31+
if (dist < layout.$.fishBehavior.cohesionDist) {
32+
cohesion = cohesion.add(other.position);
33+
cohesionCount = cohesionCount + 1;
34+
}
35+
}
36+
if (alignmentCount > 0) {
37+
alignment = alignment.mul(1 / d.f32(alignmentCount));
38+
}
39+
if (cohesionCount > 0) {
40+
cohesion = std.sub(
41+
std.mul(1 / d.f32(cohesionCount), cohesion),
42+
fishData.position,
43+
);
44+
}
45+
for (let i = 0; i < 3; i += 1) {
46+
const repulsion = d.vec3f();
47+
repulsion[i] = 1.0;
48+
49+
const axisAquariumSize = p.aquariumSize[i] / 2;
50+
const axisPosition = fishData.position[i];
51+
const distance = p.fishWallRepulsionDistance;
52+
53+
if (axisPosition > axisAquariumSize - distance) {
54+
const str = axisPosition - (axisAquariumSize - distance);
55+
wallRepulsion = wallRepulsion.sub(repulsion.mul(str));
56+
}
57+
58+
if (axisPosition < -axisAquariumSize + distance) {
59+
const str = -axisAquariumSize + distance - axisPosition;
60+
wallRepulsion = wallRepulsion.add(repulsion.mul(str));
61+
}
62+
}
63+
64+
if (layout.$.mouseRay.activated === 1) {
65+
const proj = projectPointOnLine(
66+
fishData.position,
67+
layout.$.mouseRay.line,
68+
);
69+
const diff = fishData.position.sub(proj);
70+
const limit = p.fishMouseRayRepulsionDistance;
71+
const str = std.pow(2, std.clamp(limit - std.length(diff), 0, limit)) - 1;
72+
rayRepulsion = std.normalize(diff).mul(str);
73+
}
74+
75+
let direction = d.vec3f(fishData.direction);
76+
77+
direction = direction.add(
78+
separation.mul(layout.$.fishBehavior.separationStr),
79+
);
80+
direction = direction.add(
81+
alignment.mul(layout.$.fishBehavior.alignmentStr),
82+
);
83+
direction = direction.add(
84+
cohesion.mul(layout.$.fishBehavior.cohesionStr),
85+
);
86+
direction = direction.add(
87+
wallRepulsion.mul(p.fishWallRepulsionStrength),
88+
);
89+
direction = direction.add(
90+
rayRepulsion.mul(p.fishMouseRayRepulsionStrength),
91+
);
92+
direction = std.normalize(direction).mul(
93+
std.clamp(std.length(fishData.direction), 0, 0.01),
94+
);
95+
96+
const translation = direction.mul(
97+
d.f32(std.min(999, layout.$.timePassed)) / 8,
98+
);
99+
100+
const nextFishData = layout.$.nextFishData[fishIndex];
101+
nextFishData.position = fishData.position.add(translation);
102+
nextFishData.direction = d.vec3f(direction);
103+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<style>
2+
.controls-header {
3+
font-size: 1.1rem;
4+
margin-bottom: 0.5rem;
5+
}
6+
7+
.controls {
8+
font-size: 0.8rem;
9+
}
10+
11+
#attribution {
12+
margin-top: 1rem;
13+
font-size: 0.8rem;
14+
}
15+
16+
#help {
17+
opacity: 0;
18+
position: absolute;
19+
margin: 0 auto;
20+
text-align: left;
21+
z-index: 1;
22+
background-color: rgba(0, 0, 0, 0.7);
23+
color: white;
24+
padding: 0.625rem;
25+
border-radius: 0.625rem;
26+
user-select: none;
27+
pointer-events: none;
28+
transition: opacity 0.5s;
29+
}
30+
31+
.spinner-background {
32+
inset: 0;
33+
position: absolute;
34+
display: grid;
35+
place-items: center;
36+
background-color: var(--color-tameplum-50, white);
37+
z-index: 4;
38+
}
39+
</style>
40+
41+
<div class="spinner-background">
42+
<div class="spinner">Loading...</div>
43+
</div>
44+
<div id="help">
45+
<h3 class="controls-header">Controls (click to dismiss)</h3>
46+
<p class="controls"><b>Left Mouse Button:</b> Look around</p>
47+
<p class="controls"><b>Right Mouse Button:</b> Repel fish from the cursor</p>
48+
<p id="attribution">
49+
Uses "Animated Low Poly Fish" by Sketchfab user Atlas, used under CC BY 4.0
50+
/ Modified from original
51+
</p>
52+
</div>
53+
<canvas data-fit-to-container></canvas>

0 commit comments

Comments
 (0)