Skip to content

Commit 712fbd5

Browse files
authored
docs: Mesh skinning (#1978)
1 parent e20d905 commit 712fbd5

13 files changed

Lines changed: 1164 additions & 4 deletions

File tree

apps/typegpu-docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@babel/standalone": "^7.28.6",
2121
"@dimforge/rapier2d-compat": "^0.19.3",
2222
"@loaders.gl/core": "^4.3.4",
23+
"@loaders.gl/gltf": "^4.3.4",
2324
"@loaders.gl/obj": "^4.3.4",
2425
"@monaco-editor/react": "^4.7.0",
2526
"@radix-ui/react-select": "^2.2.6",
7.69 MB
Binary file not shown.

apps/typegpu-docs/src/components/stackblitz/openInStackBlitz.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ ${example.htmlFile.content}
116116
"wgpu-matrix": "${pnpmWorkspaceYaml.catalogs.example['wgpu-matrix']}",
117117
"@loaders.gl/core": "${typegpuDocsPackageJson.dependencies['@loaders.gl/core']}",
118118
"@loaders.gl/obj": "${typegpuDocsPackageJson.dependencies['@loaders.gl/obj']}",
119+
"@loaders.gl/gltf": "${typegpuDocsPackageJson.dependencies['@loaders.gl/gltf']}",
119120
"three": "${pnpmWorkspaceYaml.catalogs.example.three}",
120121
"@typegpu/noise": "${typegpuNoisePackageJson.version}",
121122
"@typegpu/color": "${typegpuColorPackageJson.version}",
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { quat, vec3 } from 'wgpu-matrix';
2+
import type { Animation, Quat, Vec3 } from './types.ts';
3+
4+
export interface NodeTransform {
5+
translation: Vec3;
6+
hasTranslation: boolean;
7+
rotation: Quat;
8+
hasRotation: boolean;
9+
scale: Vec3;
10+
hasScale: boolean;
11+
}
12+
13+
export type NodeTransformState = NodeTransform[];
14+
15+
export function createNodeTransformState(nodeCount: number): NodeTransformState {
16+
return Array.from({ length: nodeCount }, () => ({
17+
translation: [0, 0, 0],
18+
hasTranslation: false,
19+
rotation: [0, 0, 0, 1],
20+
hasRotation: false,
21+
scale: [1, 1, 1],
22+
hasScale: false,
23+
}));
24+
}
25+
26+
export function sampleAnimationInto(
27+
animation: Animation | undefined,
28+
time: number,
29+
transforms: NodeTransformState,
30+
touchedNodes: number[],
31+
): NodeTransformState {
32+
for (const nodeIndex of touchedNodes) {
33+
const transform = transforms[nodeIndex];
34+
transform.hasTranslation = false;
35+
transform.hasRotation = false;
36+
transform.hasScale = false;
37+
}
38+
touchedNodes.length = 0;
39+
40+
if (!animation || animation.duration <= 0) {
41+
return transforms;
42+
}
43+
44+
const loopedTime = time % animation.duration;
45+
46+
for (const channel of animation.channels) {
47+
const { input: times, output: values } = animation.samplers[channel.samplerIndex];
48+
const components = channel.targetPath === 'rotation' ? 4 : 3;
49+
50+
let keyframeIndex = 0;
51+
while (keyframeIndex < times.length - 2 && loopedTime >= times[keyframeIndex + 1]) {
52+
keyframeIndex++;
53+
}
54+
55+
const startTime = times[keyframeIndex];
56+
const endTime = times[keyframeIndex + 1];
57+
const alpha =
58+
endTime > startTime
59+
? Math.max(0, Math.min(1, (loopedTime - startTime) / (endTime - startTime)))
60+
: 0;
61+
62+
const start = keyframeIndex * components;
63+
const end = (keyframeIndex + 1) * components;
64+
65+
const transform = transforms[channel.targetNode];
66+
if (!transform.hasTranslation && !transform.hasRotation && !transform.hasScale) {
67+
touchedNodes.push(channel.targetNode);
68+
}
69+
70+
if (channel.targetPath === 'rotation') {
71+
quat.slerp(
72+
values.subarray(start, start + components),
73+
values.subarray(end, end + components),
74+
alpha,
75+
transform.rotation,
76+
);
77+
transform.hasRotation = true;
78+
} else if (channel.targetPath === 'translation') {
79+
vec3.lerp(
80+
values.subarray(start, start + components),
81+
values.subarray(end, end + components),
82+
alpha,
83+
transform.translation,
84+
);
85+
transform.hasTranslation = true;
86+
} else {
87+
vec3.lerp(
88+
values.subarray(start, start + components),
89+
values.subarray(end, end + components),
90+
alpha,
91+
transform.scale,
92+
);
93+
transform.hasScale = true;
94+
}
95+
}
96+
97+
return transforms;
98+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<canvas data-fit-to-container></canvas>
2+
3+
<style>
4+
#attribution {
5+
opacity: 1;
6+
position: absolute;
7+
bottom: 0.625rem;
8+
left: 0.625rem;
9+
z-index: 2;
10+
background-color: rgba(0, 0, 0, 0.6);
11+
color: white;
12+
padding: 0.5rem 0.75rem;
13+
border-radius: 0.5rem;
14+
user-select: none;
15+
pointer-events: auto;
16+
transition: opacity 0.5s;
17+
font-size: 0.85rem;
18+
line-height: 1.5;
19+
white-space: nowrap;
20+
}
21+
22+
#attribution a {
23+
color: #87ceeb;
24+
text-decoration: none;
25+
}
26+
27+
#attribution a:hover {
28+
text-decoration: underline;
29+
}
30+
</style>
31+
32+
<div id="attribution">
33+
Mesh by
34+
<a href="https://quaternius.com/packs/universalanimationlibrary2.html" target="_blank"
35+
>Quaternius</a
36+
>
37+
</div>

0 commit comments

Comments
 (0)