Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -352,21 +352,14 @@ resizeObserver.observe(canvas);
let isDragging = false;
let prevX = 0;
let prevY = 0;
let lastPinchDist = 0;
let orbitRadius = std.length(cameraInitialPos);

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

function updateCameraOrbit(dx: number, dy: number) {
const orbitSensitivity = 0.005;
orbitYaw += -dx * orbitSensitivity;
orbitPitch += dy * orbitSensitivity;
// Clamp pitch to avoid flipping
const maxPitch = Math.PI / 2 - 0.01;
if (orbitPitch > maxPitch) orbitPitch = maxPitch;
if (orbitPitch < -maxPitch) orbitPitch = -maxPitch;
// Convert spherical coordinates to cartesian coordinates
function updateCameraPosition() {
const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch);
const newCamY = orbitRadius * Math.sin(orbitPitch);
const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch);
Expand All @@ -381,21 +374,24 @@ function updateCameraOrbit(dx: number, dy: number) {
cameraBuffer.writePartial({ view: newView, position: newCameraPos });
}

canvas.addEventListener('wheel', (event: WheelEvent) => {
event.preventDefault();
const zoomSensitivity = 0.05;
orbitRadius = std.clamp(orbitRadius + event.deltaY * zoomSensitivity, 3, 100);
const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch);
const newCamY = orbitRadius * Math.sin(orbitPitch);
const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch);
const newCameraPos = d.vec4f(newCamX, newCamY, newCamZ, 1);
const newView = m.mat4.lookAt(
newCameraPos,
d.vec3f(0, 0, 0),
d.vec3f(0, 1, 0),
d.mat4x4f(),
function updateCameraOrbit(dx: number, dy: number) {
orbitYaw += -dx * 0.005;
orbitPitch = std.clamp(
orbitPitch + dy * 0.005,
-Math.PI / 2 + 0.01,
Math.PI / 2 - 0.01,
);
cameraBuffer.writePartial({ view: newView, position: newCameraPos });
updateCameraPosition();
}

function zoomCamera(delta: number) {
orbitRadius = std.clamp(orbitRadius + delta, 3, 100);
updateCameraPosition();
}

canvas.addEventListener('wheel', (e: WheelEvent) => {
e.preventDefault();
zoomCamera(e.deltaY * 0.05);
}, { passive: false });

canvas.addEventListener('mousedown', (event) => {
Expand All @@ -404,12 +400,17 @@ canvas.addEventListener('mousedown', (event) => {
prevY = event.clientY;
});

canvas.addEventListener('touchstart', (event) => {
event.preventDefault();
if (event.touches.length === 1) {
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
if (e.touches.length === 1) {
isDragging = true;
prevX = event.touches[0].clientX;
prevY = event.touches[0].clientY;
prevX = e.touches[0].clientX;
prevY = e.touches[0].clientY;
} else if (e.touches.length === 2) {
isDragging = false;
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
lastPinchDist = Math.sqrt(dx * dx + dy * dy);
}
}, { passive: false });

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

const touchEndEventListener = () => {
isDragging = false;
const touchEndEventListener = (e: TouchEvent) => {
if (e.touches.length === 1) {
isDragging = true;
prevX = e.touches[0].clientX;
prevY = e.touches[0].clientY;
} else {
isDragging = false;
}
};
window.addEventListener('touchend', touchEndEventListener);

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

const touchMoveEventListener = (event: TouchEvent) => {
if (isDragging && event.touches.length === 1) {
event.preventDefault();
const dx = event.touches[0].clientX - prevX;
const dy = event.touches[0].clientY - prevY;
prevX = event.touches[0].clientX;
prevY = event.touches[0].clientY;

const touchMoveEventListener = (e: TouchEvent) => {
if (e.touches.length === 1 && isDragging) {
e.preventDefault();
const dx = e.touches[0].clientX - prevX;
const dy = e.touches[0].clientY - prevY;
prevX = e.touches[0].clientX;
prevY = e.touches[0].clientY;
updateCameraOrbit(dx, dy);
}
};
window.addEventListener('touchmove', touchMoveEventListener, {
passive: false,
});

canvas.addEventListener('touchmove', (e) => {
if (e.touches.length === 2) {
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const pinchDist = Math.sqrt(dx * dx + dy * dy);
zoomCamera((lastPinchDist - pinchDist) * 0.05);
lastPinchDist = pinchDist;
}
}, { passive: false });

function hideHelp() {
const helpElem = document.getElementById('help');
if (helpElem) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function setupOrbitCamera(
let isDragging = false;
let prevX = 0;
let prevY = 0;
let lastPinchDist = 0;

// mouse/touch events
canvas.addEventListener('wheel', (event: WheelEvent) => {
Expand All @@ -138,6 +139,11 @@ export function setupOrbitCamera(
isDragging = true;
prevX = event.touches[0].clientX;
prevY = event.touches[0].clientY;
} else if (event.touches.length === 2) {
isDragging = false;
const dx = event.touches[0].clientX - event.touches[1].clientX;
const dy = event.touches[0].clientY - event.touches[1].clientY;
lastPinchDist = Math.sqrt(dx * dx + dy * dy);
}
}, { passive: false });

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

const touchEndEventListener = () => {
isDragging = false;
const touchEndEventListener = (e: TouchEvent) => {
if (e.touches.length === 1) {
isDragging = true;
prevX = e.touches[0].clientX;
prevY = e.touches[0].clientY;
} else {
isDragging = false;
}
};
window.addEventListener('touchend', touchEndEventListener);

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

const touchMoveEventListener = (event: TouchEvent) => {
if (isDragging && event.touches.length === 1) {
if (event.touches.length === 1 && isDragging) {
event.preventDefault();
const dx = event.touches[0].clientX - prevX;
const dy = event.touches[0].clientY - prevY;
Expand All @@ -178,6 +190,17 @@ export function setupOrbitCamera(
passive: false,
});

canvas.addEventListener('touchmove', (e) => {
if (e.touches.length === 2) {
e.preventDefault();
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const pinchDist = Math.sqrt(dx * dx + dy * dy);
zoomCamera((lastPinchDist - pinchDist) * 0.5);
lastPinchDist = pinchDist;
}
}, { passive: false });

function cleanupCamera() {
window.removeEventListener('mouseup', mouseUpEventListener);
window.removeEventListener('mousemove', mouseMoveEventListener);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import type { IndexFlag, TgpuBuffer, TgpuRoot, VertexFlag } from 'typegpu';
import * as d from 'typegpu/data';
import type { GeometryData } from './types.ts';
import { InstanceData, VertexData } from './types.ts';

export class BoxGeometry {
static #vertexBuffer:
| (TgpuBuffer<d.WgslArray<VertexData>> & VertexFlag)
| null = null;
static #indexBuffer: (TgpuBuffer<d.WgslArray<d.U16>> & IndexFlag) | null =
null;
static #indexCount = 0;

#modelMatrix = d.mat4x4f.identity();
#position = d.vec3f(0, 0, 0);
#scale = d.vec3f(1, 1, 1);
#rotation = d.vec3f(0, 0, 0);

constructor(root: TgpuRoot) {
if (!BoxGeometry.#vertexBuffer || !BoxGeometry.#indexBuffer) {
this.#initBuffers(root);
}
}

#initBuffers(root: TgpuRoot) {
const vertices: GeometryData = [];
const indices: number[] = [];
let vertexOffset = 0;

const addFace = (
u: number,
v: number,
w: number,
udir: number,
vdir: number,
depth: number,
) => {
for (let iy = 0; iy < 2; iy++) {
for (let ix = 0; ix < 2; ix++) {
const pos = [0, 0, 0];
pos[u] = (ix - 0.5) * udir;
pos[v] = (iy - 0.5) * vdir;
pos[w] = 0.5 * Math.sign(depth);

const norm = [0, 0, 0];
norm[w] = Math.sign(depth);

vertices.push(
{
position: d.vec3f(pos[0], pos[1], pos[2]),
normal: d.vec3f(norm[0], norm[1], norm[2]),
uv: d.vec2f(ix, 1 - iy),
},
);
}
}

indices.push(
vertexOffset,
vertexOffset + 1,
vertexOffset + 2,
vertexOffset + 1,
vertexOffset + 3,
vertexOffset + 2,
);
vertexOffset += 4;
};

addFace(2, 1, 0, -1, 1, 1); // +X
addFace(2, 1, 0, 1, 1, -1); // -X
addFace(0, 2, 1, 1, 1, 1); // +Y
addFace(0, 2, 1, 1, -1, -1); // -Y
addFace(0, 1, 2, 1, 1, 1); // +Z
addFace(0, 1, 2, -1, 1, -1); // -Z

BoxGeometry.#vertexBuffer = root
.createBuffer(d.arrayOf(VertexData, vertices.length), vertices)
.$usage('vertex');
BoxGeometry.#indexBuffer = root
.createBuffer(d.arrayOf(d.u16, indices.length), indices)
.$usage('index');
BoxGeometry.#indexCount = indices.length;
}

set position(value: d.v3f) {
this.#position = value;
this.#updateModelMatrix();
}

get position() {
return this.#position;
}

set scale(value: d.v3f) {
this.#scale = value;
this.#updateModelMatrix();
}

get scale() {
return this.#scale;
}

set rotation(value: d.v3f) {
this.#rotation = value;
this.#updateModelMatrix();
}

get rotation() {
return this.#rotation;
}

get instanceData(): d.Infer<InstanceData> {
return InstanceData({
column1: this.#modelMatrix.columns[0],
column2: this.#modelMatrix.columns[1],
column3: this.#modelMatrix.columns[2],
column4: this.#modelMatrix.columns[3],
});
}

static get vertexBuffer() {
if (!BoxGeometry.#vertexBuffer) {
throw new Error('BoxGeometry buffers not initialized');
}
return BoxGeometry.#vertexBuffer;
}

static get indexBuffer() {
if (!BoxGeometry.#indexBuffer) {
throw new Error('BoxGeometry buffers not initialized');
}
return BoxGeometry.#indexBuffer;
}

static get indexCount() {
return BoxGeometry.#indexCount;
}

static clearBuffers() {
BoxGeometry.#vertexBuffer = null;
BoxGeometry.#indexBuffer = null;
}

#updateModelMatrix() {
this.#modelMatrix = d.mat4x4f
.translation(this.#position)
.mul(d.mat4x4f.rotationZ(this.#rotation.z))
.mul(d.mat4x4f.rotationY(this.#rotation.y))
.mul(d.mat4x4f.rotationX(this.#rotation.x))
.mul(d.mat4x4f.scaling(this.#scale));
}
}
Loading