Skip to content

Commit e069c4d

Browse files
committed
Smooth head rotations
1 parent e940f86 commit e069c4d

3 files changed

Lines changed: 62 additions & 31 deletions

File tree

apps/typegpu-docs/src/examples/rendering/pufferfish/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,13 @@ function draw(timestamp: number) {
9595
const invProjMat = mat4.identity(d.mat4x4f());
9696
const scale = Math.max(1, canvas.height / canvas.width);
9797
const aspect = canvas.width / canvas.height;
98-
const puffScale = 1.2 - pufferfishController.sizeSpring.value * 0.2;
98+
const puffScale = 1.5 - pufferfishController.sizeSpring.value * 0.2;
9999
mat4.scale(invProjMat, [aspect * scale, scale, 1], invProjMat);
100100
mat4.scale(invProjMat, d.vec3f(puffScale, puffScale, 1), invProjMat);
101101

102102
const invModelMat = mat4.identity(d.mat4x4f());
103-
const headPitch = Math.sin(timestamp * 0.001) * 0.2;
104-
const headYaw = Math.sin(timestamp * 0.0023) * 0.2;
105-
mat4.rotateY(invModelMat, headYaw, invModelMat);
106-
mat4.rotateX(invModelMat, headPitch, invModelMat);
103+
mat4.rotateY(invModelMat, pufferfishController.headYaw, invModelMat);
104+
mat4.rotateX(invModelMat, -pufferfishController.headPitch, invModelMat);
107105

108106
const videoTexture = device.importExternalTexture({ source: video });
109107
const frequentGroup = root.createBindGroup(frequentLayout, {

apps/typegpu-docs/src/examples/rendering/pufferfish/pufferfish-controller.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,31 @@ function extractFaceLandmarks(landmarks: NormalizedLandmark[]): FaceLandmarks {
128128
};
129129
}
130130

131+
function encroach(
132+
from: number,
133+
to: number,
134+
factorPerSecond: number,
135+
deltaSeconds: number,
136+
): number {
137+
const diff = to - from;
138+
const factor = factorPerSecond ** deltaSeconds;
139+
return from + diff * (1 - factor);
140+
}
141+
131142
export class PufferfishController {
132143
#faceLandmarker: FaceLandmarker;
133144
sizeSpring: Spring;
134145
faceLandmarks: FaceLandmarks | undefined;
146+
smoothFaceLandmarks: FaceLandmarks | undefined;
147+
148+
headYaw: number;
149+
headPitch: number;
135150

136151
constructor(faceLandmarker: FaceLandmarker) {
137152
this.#faceLandmarker = faceLandmarker;
138-
this.sizeSpring = new Spring({ damping: 10, mass: 1, stiffness: 1000 });
153+
this.sizeSpring = new Spring({ damping: 16, mass: 1, stiffness: 1000 });
154+
this.headYaw = 0;
155+
this.headPitch = 0;
139156
}
140157

141158
updatePuffScore(
@@ -157,6 +174,41 @@ export class PufferfishController {
157174

158175
update(dt: number) {
159176
this.sizeSpring.update(dt);
177+
178+
const current = this.faceLandmarks;
179+
const prev = this.smoothFaceLandmarks || current;
180+
if (!prev || !current) {
181+
return;
182+
}
183+
184+
const fromFaceOval = prev.faceOval;
185+
const toFaceOval = current.faceOval;
186+
187+
const conv = 0.002;
188+
this.smoothFaceLandmarks = {
189+
...prev,
190+
// We only smooth out the face oval for now, as it's the only landmark we use
191+
faceOval: {
192+
xMin: encroach(fromFaceOval.xMin, toFaceOval.xMin, conv, dt),
193+
xMax: encroach(fromFaceOval.xMax, toFaceOval.xMax, conv, dt),
194+
yMin: encroach(fromFaceOval.yMin, toFaceOval.yMin, conv, dt),
195+
yMax: encroach(fromFaceOval.yMax, toFaceOval.yMax, conv, dt),
196+
},
197+
};
198+
199+
const nose = current.nose;
200+
201+
// Calculate the center of the face oval
202+
const ovalX = (toFaceOval.xMin + toFaceOval.xMax) / 2;
203+
const ovalY = (toFaceOval.yMin + toFaceOval.yMax) / 2;
204+
205+
// Determining the head's orientation based on how much the nose is off-center
206+
const targetYaw = -(nose.x - ovalX) * 10;
207+
const targetPitch = (nose.y - ovalY) * 10;
208+
209+
// Smoothly approaching the target orientation
210+
this.headYaw = encroach(this.headYaw, targetYaw, 0.01, dt);
211+
this.headPitch = encroach(this.headPitch, targetPitch, 0.01, dt);
160212
}
161213
}
162214

apps/typegpu-docs/src/examples/rendering/pufferfish/shader.ts

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,6 @@ const Shape = d.struct({
5454
});
5555
type Shape = d.Infer<typeof Shape>;
5656

57-
const rotationMatrixY = (angle: number): d.m3x3f => {
58-
'use gpu';
59-
const c = std.cos(angle);
60-
const s = std.sin(angle);
61-
62-
return d.mat3x3f(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
63-
};
64-
65-
const rotationMatrixX = (angle: number): d.m3x3f => {
66-
'use gpu';
67-
const c = std.cos(angle);
68-
const s = std.sin(angle);
69-
return d.mat3x3f(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
70-
};
71-
72-
const rotationMatrixZ = (angle: number): d.m3x3f => {
73-
'use gpu';
74-
const c = std.cos(angle);
75-
const s = std.sin(angle);
76-
return d.mat3x3f(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
77-
};
78-
7957
const sdEllipsoid = (p: d.v3f, r: d.v3f): number => {
8058
'use gpu';
8159
const k0 = std.length(p.div(r));
@@ -330,6 +308,9 @@ export const fullColorFragment = tgpu['~unstable'].fragmentFn({
330308
*/
331309
const faceSize = 1.8;
332310
const faceUv = suv.mul(faceSize).mul(0.5).add(0.5);
311+
// Flip the x axis
312+
faceUv.x = 1.0 - faceUv.x;
313+
333314
if (distance < MAX_DIST) {
334315
// Hit the pufferfish
335316
// const localPos = hitPos.sub(bodyPosition);
@@ -393,11 +374,11 @@ export const sdfDebugFragment = tgpu['~unstable'].fragmentFn({
393374
out: d.vec4f,
394375
})((input) => {
395376
'use gpu';
396-
const uv = input.uv.mul(2).sub(1).mul(1.5);
377+
const uv = input.uv.mul(2).sub(1);
397378
const bodyPosition = bodyPositionAccess.$;
398379
// Ray setup
399-
const initialRo = d.vec3f(uv.x, uv.y, 0);
400-
let ro = uniformsAccess.$.invProjMat.mul(d.vec4f(initialRo, 1)).xyz;
380+
const suv = uniformsAccess.$.invProjMat.mul(d.vec4f(uv, 0, 1)).xy;
381+
let ro = d.vec3f(suv, 0);
401382
let rd = uniformsAccess.$.invProjMat.mul(d.vec4f(0, 0, 1, 0)).xyz;
402383

403384
// Transforming around the pufferfish

0 commit comments

Comments
 (0)