Skip to content

Commit

Permalink
Merge pull request #6 from JediWattson/controller
Browse files Browse the repository at this point in the history
controller and keyboard are both in their own respective files with the camera as a separate module as well
  • Loading branch information
JediWattson authored Jan 11, 2024
2 parents 7f4f9be + b1cda64 commit dc38523
Show file tree
Hide file tree
Showing 10 changed files with 5,694 additions and 455 deletions.
4,799 changes: 4,799 additions & 0 deletions example/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"next": "^14.0.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"webgpu-fun": "^0.2.19"
"webgpu-fun": "../src"
},
"devDependencies": {
"@types/node": "20.1.2",
Expand Down
1,087 changes: 721 additions & 366 deletions example/yarn.lock

Large diffs are not rendered by default.

79 changes: 20 additions & 59 deletions src/camera.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Vec3, mat4, vec3 } from "wgpu-matrix";
import { deg2Rad } from "./utils";
import { mat4, vec3 } from "wgpu-matrix";
import { rotPos } from "./utils";
import type { WebGPUFun } from "./types";

const MOVEMENT_SCALE = 5;
const FULL_CIRCLE = 360;
const MOVEMENT_DIFF = 0.05;
const MAX_FOLCRUM = 89;
const upVec = vec3.create(0, 0, 1);

Expand All @@ -18,69 +17,31 @@ export default function initCamera(device: GPUDevice) {

const eulers = [0, 0];
const position = vec3.create(-2, 0, 0.5);
const forwards = vec3.create();
const up = vec3.create();
const forwards = vec3.create(...rotPos(eulers));
const side = vec3.create();
const target = vec3.create();
const view = mat4.create();

function setMovement() {
vec3.copy([
Math.cos(deg2Rad(eulers[1])) * Math.cos(deg2Rad(eulers[0])),
Math.sin(deg2Rad(eulers[1])) * Math.cos(deg2Rad(eulers[0])),
Math.sin(deg2Rad(eulers[0]))
], forwards)
vec3.cross(forwards, upVec, side);
vec3.cross(side, forwards, up);
vec3.add(position, forwards, target);
mat4.lookAt(position, target, up, view);
device.queue.writeBuffer(uniBuffer, 0, <ArrayBuffer>view);
}

setMovement();

const keysDown: { [key: string]: number } = {}
function handleDiff(direction: string[], vector: Vec3) {
if (direction.every(k => !(k in keysDown))) return;

let diff = MOVEMENT_DIFF
if (direction[1] in keysDown || keysDown[direction[0]] > keysDown[direction[1]])
diff = -MOVEMENT_DIFF
vec3.addScaled(position, vector, diff, position);
}

let frameNumber: number
function moveCamera() {
if (Object.keys(keysDown).length === 0) return;

handleDiff(['d', 'a'], side)
handleDiff(['w', 's'], forwards)

setMovement()
frameNumber = requestAnimationFrame(moveCamera);
}

const camera: WebGPUFun.CameraType = {
move(key, isUpPress = false) {
if (!['w', 'a', 's', 'd'].includes(key)) return

if (isUpPress) {
delete keysDown[key]
return;
}

keysDown[key] = Object.keys(keysDown).length
cancelAnimationFrame(frameNumber)
frameNumber = requestAnimationFrame(moveCamera)
setMovement() {
vec3.cross(forwards, upVec, side);
const up = vec3.cross(side, forwards);
const target = vec3.add(position, forwards);
const view = mat4.lookAt(position, target, up);
device.queue.writeBuffer(uniBuffer, 0, <ArrayBuffer>view);
},
move(diff: number, isStrafe?: boolean) {
const vector = isStrafe ? side : forwards;
vec3.addScaled(position, vector, diff, position);
},
rotate({ movementX, movementY }: { movementX: number, movementY: number }) {
eulers[0] = Math.min(MAX_FOLCRUM, Math.max(-MAX_FOLCRUM, eulers[0] - movementY / MOVEMENT_SCALE));
eulers[1] -= (movementX / MOVEMENT_SCALE) % FULL_CIRCLE;
setMovement();
rotate({ y, z }: WebGPUFun.RotationalType) {
if (y !== undefined)
eulers[1] -= (y / MOVEMENT_SCALE) % FULL_CIRCLE;
if (z !== undefined)
eulers[0] = Math.min(MAX_FOLCRUM, Math.max(-MAX_FOLCRUM, eulers[0] - z / MOVEMENT_SCALE));
vec3.copy(rotPos(eulers), forwards)
},
reset() {
eulers.fill(0)
setMovement();
this.setMovement();
}
}

Expand Down
35 changes: 35 additions & 0 deletions src/events/gamepad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { WebGPUFun } from "../types";

const equ = (x: number) => (Math.abs(x) / (Math.abs(x) + 1)) * 222

const makeGamepadHandler = (camera: WebGPUFun.CameraType) => {
let lastTime = Date.now();
function pollGamepad() {
if (navigator.getGamepads().length === 0) return;
const gamepad = navigator.getGamepads()[0];

const dt = (Date.now() - lastTime) / 100;
gamepad?.axes.forEach((a, i) => {
if (Math.abs(a) < 0.1) return;
const diff = (a > 0 ? 1 : -1) * dt;
const dynRot = equ(a)
if (i === 0)
camera.move(diff, true);
if (i === 1)
camera.move(-diff);
if (i === 2)
camera.rotate({ y: diff * dynRot });
if (i === 3)
camera.rotate({ z: diff * dynRot });
})

camera.setMovement()
lastTime = Date.now();
requestAnimationFrame(() => pollGamepad());
}

window.addEventListener("gamepadconnected", pollGamepad);

}

export default makeGamepadHandler;
96 changes: 96 additions & 0 deletions src/events/keyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { WebGPUFun } from "../types";

const MOVEMENT_DIFF = 0.01;

const handleKeys = (camera: WebGPUFun.CameraType) => {
camera.setMovement()
let frameId: number | null = null;
const strife: string[] = []
const forward: string[] = []
return (key: string, isUpPress?: boolean) => {
if (!['w', 'a', 's', 'd'].includes(key)) return
const isStrife = ['a', 'd'].includes(key)

if (isUpPress) {
if (isStrife && strife.includes(key))
strife.splice(strife.indexOf(key), 1)
else if(!isStrife && forward.includes(key))
forward.splice(forward.indexOf(key), 1)

if (forward.length === 0 && strife.length === 0 && frameId !== null) {
cancelAnimationFrame(frameId)
frameId = null;
}
return;
}

if (isStrife && !strife.includes(key)) strife.unshift(key)
else if(!isStrife && !forward.includes(key)) forward.unshift(key)

let lastTime = Date.now()
const onKey = () => {
const dx = Date.now() - lastTime
if (strife.length > 0) {
const diff = strife[0] === 'd' ? MOVEMENT_DIFF : -MOVEMENT_DIFF
camera.move(diff*dx, true)
}

if (forward.length > 0) {
const diff = forward[0] === 'w' ? MOVEMENT_DIFF : -MOVEMENT_DIFF
camera.move(diff*dx)
}

camera.setMovement()
lastTime = Date.now()
frameId = requestAnimationFrame(onKey)
}

if (frameId === null)
frameId = requestAnimationFrame(onKey)
}
}

const makeGamePadeEvents = (init: () => void, cleanup: () => void) => {
const events = [
{ event: 'gamepadconnected', cb: cleanup },
{ event: 'gamepaddisconnected', cb: init }
] as WebGPUFun.MakeEventsType

init()
events.forEach(
({ event: ev, cb }) => window.addEventListener(ev, cb)
)
}

const makeKeyboardEvents = (canvas: HTMLCanvasElement, camera: WebGPUFun.CameraType) => {
const handleKey = handleKeys(camera)
const events = [
{ event: 'keydown', cb: e => handleKey((e as KeyboardEvent).key) },
{ event: 'keyup', cb: e => handleKey((e as KeyboardEvent).key, true) },
{ event: 'click', cb: () => canvas.requestPointerLock() },
{ event: 'mouseout', cb: () => camera.reset() },
{ event: 'mousemove', cb: e => {
if(!document.pointerLockElement) return;
const { movementX: y, movementY: z } = e as MouseEvent;
camera.rotate({ y, z });
camera.setMovement()
}}
] as WebGPUFun.MakeEventsType


const init = () => events.forEach(
({ event: ev, cb }) => canvas.addEventListener(ev, cb)
)

const cleanup = () => {
events.forEach(
({ event: ev, cb }) => canvas.removeEventListener(ev, cb)
)
}

makeGamePadeEvents(init, cleanup)

return cleanup
}

export default makeKeyboardEvents;
26 changes: 11 additions & 15 deletions src/hooks/useRender.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
import { useEffect, useRef } from "react";
import { makeEvents } from "../utils";

import loadAssets from "../loadAssets";
import makeKeyboardEvents from "../events/keyboard";
import makeGamepadHandler from "../events/gamepad";
import { WebGPUFun } from "../types";


function useRender(canvasRef: { current: HTMLCanvasElement | null }, assets: Partial<WebGPUFun.BufferPipelineType>[]) {
const cleanupRef = useRef<() => void>(() => {});
const handleLoad = async () => {
if (canvasRef.current === null) return;

const ref = canvasRef.current
const context = ref.getContext('webgpu') as GPUCanvasContext;
const obj = await loadAssets(context, assets);
if (!obj) return;
const scene = await loadAssets(context, assets);
if (!scene) return;

const { camera, cleanup } = obj;
const events = makeEvents(ref, camera);

events.forEach(
({ event: ev, cb }) => ref.addEventListener(ev, cb)
)

const { camera, cleanupScene } = scene;
const cleanupEvents = makeKeyboardEvents(ref, camera);
makeGamepadHandler(camera);
cleanupRef.current = () => {
cleanup();
events.forEach(
({ event: ev, cb }) => ref.removeEventListener(ev, cb)
)
cleanupScene();
cleanupEvents()
};

}

useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/loadAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ export default async function loadAssets(contextRef: GPUCanvasContext, assets: P
}))

const renderOpts = makeRunPipelineOpts()
const cleanup = runPipeline(
const cleanupScene = runPipeline(
device,
contextRef,
pipelines,
renderOpts
);

return { camera, cleanup }
return { camera, cleanupScene }
}
6 changes: 4 additions & 2 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ declare namespace WebGPUFun {
bufferCb: (device: GPUDevice, buffer: GPUBuffer) => AssetBufferType;
}

export type RotationalType = { y?: number, z?: number }
interface CameraType {
setMovement: () => void;
reset: () => void;
move: (key: string, isUpPress?: boolean) => void;
rotate: (e: MouseEvent) => void;
move: (diff: number, isStrafe?: boolean) => void;
rotate: ({ y, z }: RotationalType) => void;
}

interface AssetBufferType {
Expand Down
15 changes: 5 additions & 10 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import type { WebGPUFun } from "./types";

export function deg2Rad(theta: number) : number {
function deg2Rad(theta: number) : number {
return theta * Math.PI / 180;
}

export const makeEvents = (canvas: HTMLCanvasElement, camera: WebGPUFun.CameraType): WebGPUFun.MakeEventsType => [
{ event: 'keydown', cb: e => camera.move((e as KeyboardEvent).key) },
{ event: 'keyup', cb: e => camera.move((e as KeyboardEvent).key, true) },
{ event: 'click', cb: () => canvas.requestPointerLock() },
{ event: 'mouseout', cb: () => camera.reset() },
{ event: 'mousemove', cb: e => {
if(!document.pointerLockElement) return;
camera?.rotate(e as MouseEvent);
}}
export const rotPos = (eulers: number[]) => [
Math.cos(deg2Rad(eulers[1])) * Math.cos(deg2Rad(eulers[0])),
Math.sin(deg2Rad(eulers[1])) * Math.cos(deg2Rad(eulers[0])),
Math.sin(deg2Rad(eulers[0]))
]

export function makeBindGroup(
Expand Down

0 comments on commit dc38523

Please sign in to comment.