Skip to content

Commit 61f40f7

Browse files
authored
docs/fix: Add point light shadow example (#1937)
1 parent 7606644 commit 61f40f7

File tree

22 files changed

+1584
-134
lines changed

22 files changed

+1584
-134
lines changed

apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -352,21 +352,14 @@ resizeObserver.observe(canvas);
352352
let isDragging = false;
353353
let prevX = 0;
354354
let prevY = 0;
355+
let lastPinchDist = 0;
355356
let orbitRadius = std.length(cameraInitialPos);
356357

357358
// Yaw and pitch angles facing the origin.
358359
let orbitYaw = Math.atan2(cameraInitialPos.x, cameraInitialPos.z);
359360
let orbitPitch = Math.asin(cameraInitialPos.y / orbitRadius);
360361

361-
function updateCameraOrbit(dx: number, dy: number) {
362-
const orbitSensitivity = 0.005;
363-
orbitYaw += -dx * orbitSensitivity;
364-
orbitPitch += dy * orbitSensitivity;
365-
// Clamp pitch to avoid flipping
366-
const maxPitch = Math.PI / 2 - 0.01;
367-
if (orbitPitch > maxPitch) orbitPitch = maxPitch;
368-
if (orbitPitch < -maxPitch) orbitPitch = -maxPitch;
369-
// Convert spherical coordinates to cartesian coordinates
362+
function updateCameraPosition() {
370363
const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch);
371364
const newCamY = orbitRadius * Math.sin(orbitPitch);
372365
const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch);
@@ -381,21 +374,24 @@ function updateCameraOrbit(dx: number, dy: number) {
381374
cameraBuffer.writePartial({ view: newView, position: newCameraPos });
382375
}
383376

384-
canvas.addEventListener('wheel', (event: WheelEvent) => {
385-
event.preventDefault();
386-
const zoomSensitivity = 0.05;
387-
orbitRadius = std.clamp(orbitRadius + event.deltaY * zoomSensitivity, 3, 100);
388-
const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch);
389-
const newCamY = orbitRadius * Math.sin(orbitPitch);
390-
const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch);
391-
const newCameraPos = d.vec4f(newCamX, newCamY, newCamZ, 1);
392-
const newView = m.mat4.lookAt(
393-
newCameraPos,
394-
d.vec3f(0, 0, 0),
395-
d.vec3f(0, 1, 0),
396-
d.mat4x4f(),
377+
function updateCameraOrbit(dx: number, dy: number) {
378+
orbitYaw += -dx * 0.005;
379+
orbitPitch = std.clamp(
380+
orbitPitch + dy * 0.005,
381+
-Math.PI / 2 + 0.01,
382+
Math.PI / 2 - 0.01,
397383
);
398-
cameraBuffer.writePartial({ view: newView, position: newCameraPos });
384+
updateCameraPosition();
385+
}
386+
387+
function zoomCamera(delta: number) {
388+
orbitRadius = std.clamp(orbitRadius + delta, 3, 100);
389+
updateCameraPosition();
390+
}
391+
392+
canvas.addEventListener('wheel', (e: WheelEvent) => {
393+
e.preventDefault();
394+
zoomCamera(e.deltaY * 0.05);
399395
}, { passive: false });
400396

401397
canvas.addEventListener('mousedown', (event) => {
@@ -404,12 +400,17 @@ canvas.addEventListener('mousedown', (event) => {
404400
prevY = event.clientY;
405401
});
406402

407-
canvas.addEventListener('touchstart', (event) => {
408-
event.preventDefault();
409-
if (event.touches.length === 1) {
403+
canvas.addEventListener('touchstart', (e) => {
404+
e.preventDefault();
405+
if (e.touches.length === 1) {
410406
isDragging = true;
411-
prevX = event.touches[0].clientX;
412-
prevY = event.touches[0].clientY;
407+
prevX = e.touches[0].clientX;
408+
prevY = e.touches[0].clientY;
409+
} else if (e.touches.length === 2) {
410+
isDragging = false;
411+
const dx = e.touches[0].clientX - e.touches[1].clientX;
412+
const dy = e.touches[0].clientY - e.touches[1].clientY;
413+
lastPinchDist = Math.sqrt(dx * dx + dy * dy);
413414
}
414415
}, { passive: false });
415416

@@ -418,8 +419,14 @@ const mouseUpEventListener = () => {
418419
};
419420
window.addEventListener('mouseup', mouseUpEventListener);
420421

421-
const touchEndEventListener = () => {
422-
isDragging = false;
422+
const touchEndEventListener = (e: TouchEvent) => {
423+
if (e.touches.length === 1) {
424+
isDragging = true;
425+
prevX = e.touches[0].clientX;
426+
prevY = e.touches[0].clientY;
427+
} else {
428+
isDragging = false;
429+
}
423430
};
424431
window.addEventListener('touchend', touchEndEventListener);
425432

@@ -435,21 +442,31 @@ const mouseMoveEventListener = (event: MouseEvent) => {
435442
};
436443
window.addEventListener('mousemove', mouseMoveEventListener);
437444

438-
const touchMoveEventListener = (event: TouchEvent) => {
439-
if (isDragging && event.touches.length === 1) {
440-
event.preventDefault();
441-
const dx = event.touches[0].clientX - prevX;
442-
const dy = event.touches[0].clientY - prevY;
443-
prevX = event.touches[0].clientX;
444-
prevY = event.touches[0].clientY;
445-
445+
const touchMoveEventListener = (e: TouchEvent) => {
446+
if (e.touches.length === 1 && isDragging) {
447+
e.preventDefault();
448+
const dx = e.touches[0].clientX - prevX;
449+
const dy = e.touches[0].clientY - prevY;
450+
prevX = e.touches[0].clientX;
451+
prevY = e.touches[0].clientY;
446452
updateCameraOrbit(dx, dy);
447453
}
448454
};
449455
window.addEventListener('touchmove', touchMoveEventListener, {
450456
passive: false,
451457
});
452458

459+
canvas.addEventListener('touchmove', (e) => {
460+
if (e.touches.length === 2) {
461+
e.preventDefault();
462+
const dx = e.touches[0].clientX - e.touches[1].clientX;
463+
const dy = e.touches[0].clientY - e.touches[1].clientY;
464+
const pinchDist = Math.sqrt(dx * dx + dy * dy);
465+
zoomCamera((lastPinchDist - pinchDist) * 0.05);
466+
lastPinchDist = pinchDist;
467+
}
468+
}, { passive: false });
469+
453470
function hideHelp() {
454471
const helpElem = document.getElementById('help');
455472
if (helpElem) {

apps/typegpu-docs/src/examples/rendering/phong-reflection/setup-orbit-camera.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export function setupOrbitCamera(
119119
let isDragging = false;
120120
let prevX = 0;
121121
let prevY = 0;
122+
let lastPinchDist = 0;
122123

123124
// mouse/touch events
124125
canvas.addEventListener('wheel', (event: WheelEvent) => {
@@ -138,6 +139,11 @@ export function setupOrbitCamera(
138139
isDragging = true;
139140
prevX = event.touches[0].clientX;
140141
prevY = event.touches[0].clientY;
142+
} else if (event.touches.length === 2) {
143+
isDragging = false;
144+
const dx = event.touches[0].clientX - event.touches[1].clientX;
145+
const dy = event.touches[0].clientY - event.touches[1].clientY;
146+
lastPinchDist = Math.sqrt(dx * dx + dy * dy);
141147
}
142148
}, { passive: false });
143149

@@ -146,8 +152,14 @@ export function setupOrbitCamera(
146152
};
147153
window.addEventListener('mouseup', mouseUpEventListener);
148154

149-
const touchEndEventListener = () => {
150-
isDragging = false;
155+
const touchEndEventListener = (e: TouchEvent) => {
156+
if (e.touches.length === 1) {
157+
isDragging = true;
158+
prevX = e.touches[0].clientX;
159+
prevY = e.touches[0].clientY;
160+
} else {
161+
isDragging = false;
162+
}
151163
};
152164
window.addEventListener('touchend', touchEndEventListener);
153165

@@ -164,7 +176,7 @@ export function setupOrbitCamera(
164176
window.addEventListener('mousemove', mouseMoveEventListener);
165177

166178
const touchMoveEventListener = (event: TouchEvent) => {
167-
if (isDragging && event.touches.length === 1) {
179+
if (event.touches.length === 1 && isDragging) {
168180
event.preventDefault();
169181
const dx = event.touches[0].clientX - prevX;
170182
const dy = event.touches[0].clientY - prevY;
@@ -178,6 +190,17 @@ export function setupOrbitCamera(
178190
passive: false,
179191
});
180192

193+
canvas.addEventListener('touchmove', (e) => {
194+
if (e.touches.length === 2) {
195+
e.preventDefault();
196+
const dx = e.touches[0].clientX - e.touches[1].clientX;
197+
const dy = e.touches[0].clientY - e.touches[1].clientY;
198+
const pinchDist = Math.sqrt(dx * dx + dy * dy);
199+
zoomCamera((lastPinchDist - pinchDist) * 0.5);
200+
lastPinchDist = pinchDist;
201+
}
202+
}, { passive: false });
203+
181204
function cleanupCamera() {
182205
window.removeEventListener('mouseup', mouseUpEventListener);
183206
window.removeEventListener('mousemove', mouseMoveEventListener);
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import type { IndexFlag, TgpuBuffer, TgpuRoot, VertexFlag } from 'typegpu';
2+
import * as d from 'typegpu/data';
3+
import type { GeometryData } from './types.ts';
4+
import { InstanceData, VertexData } from './types.ts';
5+
6+
export class BoxGeometry {
7+
static #vertexBuffer:
8+
| (TgpuBuffer<d.WgslArray<VertexData>> & VertexFlag)
9+
| null = null;
10+
static #indexBuffer: (TgpuBuffer<d.WgslArray<d.U16>> & IndexFlag) | null =
11+
null;
12+
static #indexCount = 0;
13+
14+
#modelMatrix = d.mat4x4f.identity();
15+
#position = d.vec3f(0, 0, 0);
16+
#scale = d.vec3f(1, 1, 1);
17+
#rotation = d.vec3f(0, 0, 0);
18+
19+
constructor(root: TgpuRoot) {
20+
if (!BoxGeometry.#vertexBuffer || !BoxGeometry.#indexBuffer) {
21+
this.#initBuffers(root);
22+
}
23+
}
24+
25+
#initBuffers(root: TgpuRoot) {
26+
const vertices: GeometryData = [];
27+
const indices: number[] = [];
28+
let vertexOffset = 0;
29+
30+
const addFace = (
31+
u: number,
32+
v: number,
33+
w: number,
34+
udir: number,
35+
vdir: number,
36+
depth: number,
37+
) => {
38+
for (let iy = 0; iy < 2; iy++) {
39+
for (let ix = 0; ix < 2; ix++) {
40+
const pos = [0, 0, 0];
41+
pos[u] = (ix - 0.5) * udir;
42+
pos[v] = (iy - 0.5) * vdir;
43+
pos[w] = 0.5 * Math.sign(depth);
44+
45+
const norm = [0, 0, 0];
46+
norm[w] = Math.sign(depth);
47+
48+
vertices.push(
49+
{
50+
position: d.vec3f(pos[0], pos[1], pos[2]),
51+
normal: d.vec3f(norm[0], norm[1], norm[2]),
52+
uv: d.vec2f(ix, 1 - iy),
53+
},
54+
);
55+
}
56+
}
57+
58+
indices.push(
59+
vertexOffset,
60+
vertexOffset + 1,
61+
vertexOffset + 2,
62+
vertexOffset + 1,
63+
vertexOffset + 3,
64+
vertexOffset + 2,
65+
);
66+
vertexOffset += 4;
67+
};
68+
69+
addFace(2, 1, 0, -1, 1, 1); // +X
70+
addFace(2, 1, 0, 1, 1, -1); // -X
71+
addFace(0, 2, 1, 1, 1, 1); // +Y
72+
addFace(0, 2, 1, 1, -1, -1); // -Y
73+
addFace(0, 1, 2, 1, 1, 1); // +Z
74+
addFace(0, 1, 2, -1, 1, -1); // -Z
75+
76+
BoxGeometry.#vertexBuffer = root
77+
.createBuffer(d.arrayOf(VertexData, vertices.length), vertices)
78+
.$usage('vertex');
79+
BoxGeometry.#indexBuffer = root
80+
.createBuffer(d.arrayOf(d.u16, indices.length), indices)
81+
.$usage('index');
82+
BoxGeometry.#indexCount = indices.length;
83+
}
84+
85+
set position(value: d.v3f) {
86+
this.#position = value;
87+
this.#updateModelMatrix();
88+
}
89+
90+
get position() {
91+
return this.#position;
92+
}
93+
94+
set scale(value: d.v3f) {
95+
this.#scale = value;
96+
this.#updateModelMatrix();
97+
}
98+
99+
get scale() {
100+
return this.#scale;
101+
}
102+
103+
set rotation(value: d.v3f) {
104+
this.#rotation = value;
105+
this.#updateModelMatrix();
106+
}
107+
108+
get rotation() {
109+
return this.#rotation;
110+
}
111+
112+
get instanceData(): d.Infer<InstanceData> {
113+
return InstanceData({
114+
column1: this.#modelMatrix.columns[0],
115+
column2: this.#modelMatrix.columns[1],
116+
column3: this.#modelMatrix.columns[2],
117+
column4: this.#modelMatrix.columns[3],
118+
});
119+
}
120+
121+
static get vertexBuffer() {
122+
if (!BoxGeometry.#vertexBuffer) {
123+
throw new Error('BoxGeometry buffers not initialized');
124+
}
125+
return BoxGeometry.#vertexBuffer;
126+
}
127+
128+
static get indexBuffer() {
129+
if (!BoxGeometry.#indexBuffer) {
130+
throw new Error('BoxGeometry buffers not initialized');
131+
}
132+
return BoxGeometry.#indexBuffer;
133+
}
134+
135+
static get indexCount() {
136+
return BoxGeometry.#indexCount;
137+
}
138+
139+
static clearBuffers() {
140+
BoxGeometry.#vertexBuffer = null;
141+
BoxGeometry.#indexBuffer = null;
142+
}
143+
144+
#updateModelMatrix() {
145+
this.#modelMatrix = d.mat4x4f
146+
.translation(this.#position)
147+
.mul(d.mat4x4f.rotationZ(this.#rotation.z))
148+
.mul(d.mat4x4f.rotationY(this.#rotation.y))
149+
.mul(d.mat4x4f.rotationX(this.#rotation.x))
150+
.mul(d.mat4x4f.scaling(this.#scale));
151+
}
152+
}

0 commit comments

Comments
 (0)